10 KiB
Fix Agent Tool Naming, Prompt Catalog, Timezone, Channel Build Arg, and WebSocket Reconnect Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Resolve five production issues across AI runtime tool execution, prompt capability catalogs, timestamp timezone handling, Docker image channel building, and frontend WebSocket reconnection.
Architecture:
Sanitize wire function names in the AI agent loop so providers accepting only ^[a-zA-Z0-9_-]+$ succeed while mapping back to canonical codes on execution.
Align prompt catalog strings with direct function names and explicit instructions.
Normalize timestamps to UTC when timezone offsets are missing and emit RFC3339 from the backend.
Support NEXT_PUBLIC_OPEN_IM_CHANNEL_ID build argument in Dockerfile and ensure beforeConnect token validation precedes WebSocket connections.
Tech Stack: Go 1.26, Next.js 16 (React 19, TypeScript), WebSocket, Docker
Spec: User prompt requirements (5 targeted fixes)
Global Constraints
- Preserve all existing staged and user-owned worktree changes.
- Follow layer boundaries: models -> repositories -> services -> handlers.
- Portable SQL / SQLite & MySQL compatibility.
- Format all Go files with
gofmt. - TypeScript checks must pass with
cd web && pnpm typecheck. - Maintain test coverage across changed backend and frontend surfaces.
Review Focus
- Function name sanitization must only alter the wire definition, preserving registry, whitelist, and audit codes.
- Direct invocation and tool_search calls must both resolve to the correct capability code.
- Timestamp parsing must treat strings lacking timezone offsets as UTC while keeping explicit offsets untouched.
- Reconnection lifecycle must not create duplicate sockets or run stale connection loops when disconnected mid-flight.
- Docker ARG must properly inject into the Next.js builder stage.
Task 1: Sanitize wire tool function names and reverse mapping on call
Files:
- Modify:
internal/pkg/toolx/builtin_tools.go - Modify:
internal/pkg/toolx/builtin_tools_test.go - Modify:
internal/ai/application/runtime/agent_loop_engine.go - Modify:
internal/ai/application/runtime/agent_loop_engine_test.go
Interfaces:
-
Consumes:
RegisteredToolSpecs,ToolSpec,ai.ToolDefinition,ai.ToolCall -
Produces:
toolx.SanitizeToolFunctionName(toolCode string) string,toolx.ResolveToolFunctionName(name string) string -
Step 1: Write the failing tests for tool function name sanitization and resolution
In internal/pkg/toolx/builtin_tools_test.go, add unit tests:
func TestSanitizeToolFunctionName(t *testing.T) {
cases := map[string]string{
"builtin/knowledge_retrieve": "builtin_knowledge_retrieve",
"graph/triage_service_request": "graph_triage_service_request",
"skill/12": "skill_12",
"workflow/99": "workflow_99",
"tool_search": "tool_search",
}
for input, expected := range cases {
if got := SanitizeToolFunctionName(input); got != expected {
t.Errorf("SanitizeToolFunctionName(%q) = %q, want %q", input, got, expected)
}
}
}
func TestResolveToolFunctionName(t *testing.T) {
cases := map[string]string{
"builtin_knowledge_retrieve": "builtin/knowledge_retrieve",
"graph_triage_service_request": "graph/triage_service_request",
"knowledge_retrieve": "builtin/knowledge_retrieve",
"skill_12": "skill/12",
"workflow/99": "workflow/99",
"workflow_99": "workflow/99",
"tool_search": "tool_search",
}
for input, expected := range cases {
if got := ResolveToolFunctionName(input); got != expected {
t.Errorf("ResolveToolFunctionName(%q) = %q, want %q", input, got, expected)
}
}
}
- Step 2: Run test to verify it fails
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/pkg/toolx -run TestSanitizeToolFunctionName
Expected: Compilation failure or FAIL (functions undefined).
- Step 3: Implement
SanitizeToolFunctionNameandResolveToolFunctionNameininternal/pkg/toolx/builtin_tools.go
Implement character sanitization (/ and any non [a-zA-Z0-9_-] runes mapped to _), spec code mapping, and prefix resolution for skill_, workflow_, builtin_, graph_.
- Step 4: Update
internal/ai/application/runtime/agent_loop_engine.go
In agentLoopToolDefinitions:
Register capability definitions using toolx.SanitizeToolFunctionName(code) as definition.Name.
In resolveAgentLoopToolCall:
Resolve call.Name using toolx.ResolveToolFunctionName(toolCode).
In agent_loop_engine_test.go:
Update TestAgentLoopRegistersDirectCapabilityAliases and add test verifying direct sanitized tool call execution.
- Step 5: Run tests to verify they pass
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/pkg/toolx
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/ai/application/runtime
Expected: PASS
Task 2: Update prompt catalog with | direct function: <sanitized> and instruction prohibition
Files:
- Modify:
internal/ai/application/runtime/agent_turn.go - Modify:
internal/ai/application/runtime/agent_loop_engine_test.go
Interfaces:
-
Consumes:
toolx.SanitizeToolFunctionName -
Produces: Prompt catalog lines including
| direct function: <sanitized>and system prompt instructions forbidding capability codes with slashes as direct function names. -
Step 1: Write test checking catalog format and instructions
In internal/ai/application/runtime/agent_loop_engine_test.go, add test asserting catalog items in prepareTurn contain | direct function: and prompt specifies prohibition.
- Step 2: Run test to verify it fails
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/ai/application/runtime -run TestAgentTurnCatalogFormat
Expected: FAIL
- Step 3: Implement catalog line updates and system prompt instruction in
internal/ai/application/runtime/agent_turn.go
For Builtin, Skill, Workflow, and MCP entries in catalog, append | direct function: <sanitized>.
In systemPrompt, add instruction: Never pass a capability code containing slashes as a tool function name; use the exact direct function name provided in the catalog or call tool_search with toolCode.
- Step 4: Run test to verify it passes
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/ai/application/runtime
Expected: PASS
Task 3: Timestamp parsing with UTC fallback & backend RFC3339 formatting
Files:
- Modify:
web/lib/utils.ts - Modify:
web/lib/api/im.ts - Modify:
web/lib/support-conversation-list.ts - Modify:
internal/services/customer_session_service.go - Modify:
internal/services/ws_service.go - Modify:
internal/services/auth_service.go - Modify:
web/lib/support-conversation-list.test.mjs
Interfaces:
-
Consumes: Raw date/time strings
-
Produces:
parseApiTimestamp(value?: string | number | Date | null): numberinweb/lib/utils.ts, RFC3339 timestamps in backend responses. -
Step 1: Write tests for
parseApiTimestampinweb/lib/support-conversation-list.test.mjs(or a dedicated test)
Assert:
-
parseApiTimestamp("2026-10-11 12:00:00")parses as UTC (Date.parse("2026-10-11T12:00:00Z")). -
parseApiTimestamp("2026-10-11T12:00:00+03:00")preserves+03:00offset. -
parseApiTimestamp("2026-10-11T12:00:00Z")preservesZ. -
Invalid strings return
0. -
Step 2: Run test to verify it fails
Run: node --test web/lib/support-conversation-list.test.mjs
Expected: FAIL (parseApiTimestamp is not defined or fails).
- Step 3: Implement
parseApiTimestampinweb/lib/utils.ts
Export parseApiTimestamp: if missing timezone offset (/(Z|[+-]\d{2}(?::?\d{2})?)$/i), append Z to treat as UTC.
Use in web/lib/api/im.ts for parseExpiresAt.
Use in web/lib/support-conversation-list.ts for recencyTimestamp.
- Step 4: Update backend date formatting to
time.RFC3339
In:
-
internal/services/customer_session_service.golines 80 and 164 -
internal/services/ws_service.goline 163 -
internal/services/auth_service.goline 309 Use.UTC().Format(time.RFC3339)(or.Format(time.RFC3339)). -
Step 5: Run frontend and backend tests
Run: node --test web/lib/support-conversation-list.test.mjs
Run: & "C:\Program Files\Go\bin\go.exe" test ./internal/services
Expected: PASS
Task 4: Add ARG NEXT_PUBLIC_OPEN_IM_CHANNEL_ID in Dockerfile
Files:
- Modify:
Dockerfile
Interfaces:
-
Docker build argument
NEXT_PUBLIC_OPEN_IM_CHANNEL_IDavailable toweb-builderstage. -
Step 1: Update
Dockerfile
In web-builder stage before RUN pnpm build:sdk && pnpm build, add:
ARG NEXT_PUBLIC_OPEN_IM_CHANNEL_ID=""
ENV NEXT_PUBLIC_OPEN_IM_CHANNEL_ID=${NEXT_PUBLIC_OPEN_IM_CHANNEL_ID}
- Step 2: Verify
Dockerfilesyntax
Check syntax and confirm no regressions.
Task 5: WebSocket connection lifecycle with beforeConnect: () => ensureCustomerSession()
Files:
- Modify:
web/lib/realtime-connection.ts - Modify:
web/lib/stores/support-chat.ts - Create / Update:
web/lib/realtime-connection.test.mjs
Interfaces:
-
Consumes:
options.beforeConnect -
Produces: Guaranteed customer session exchange/refresh before WebSocket handshake.
-
Step 1: Write test in
web/lib/realtime-connection.test.mjs
Verify that createRealtimeConnectionManager:
-
Awaits
beforeConnectbefore callingcreateSocket. -
If
beforeConnectfails, reports connect error, enters disconnected status, and schedules reconnect. -
Re-runs
beforeConnecton reconnect. -
Step 2: Run test to verify it fails
Run: node --test web/lib/realtime-connection.test.mjs
Expected: FAIL
- Step 3: Implement
beforeConnectinweb/lib/realtime-connection.tsand wire intoweb/lib/stores/support-chat.ts
Update RealtimeConnectionManagerOptions and connect() implementation.
Pass beforeConnect: () => ensureCustomerSession() in useSupportChatStore.
- Step 4: Run tests and typecheck
Run: node --test web/lib/realtime-connection.test.mjs
Run: cd web && pnpm typecheck
Expected: PASS