@oar-sh/oar 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/extensions/web-relay/README.md +190 -0
- package/.github/extensions/web-relay/extension.mjs +972 -0
- package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
- package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
- package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
- package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
- package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
- package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
- package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
- package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
- package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
- package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
- package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
- package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
- package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
- package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
- package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
- package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
- package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
- package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
- package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
- package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
- package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
- package/CHANGELOG.md +25 -0
- package/LICENSE +21 -0
- package/README.md +717 -0
- package/bin/bat/oar.bat +46 -0
- package/bin/oar.js +665 -0
- package/package.json +68 -0
- package/server/README.md +1379 -0
- package/server/claude-worker/claude-attachments.mjs +134 -0
- package/server/claude-worker/claude-preview-tool.mjs +57 -0
- package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
- package/server/claude-worker/claude-session-process.mjs +2391 -0
- package/server/claude-worker/claude-session-test-harness.mjs +236 -0
- package/server/claude-worker/claude-session-worker.mjs +186 -0
- package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
- package/server/claude-worker/claude-turn-publisher.mjs +291 -0
- package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
- package/server/claude-worker/workflow-progress-digest.mjs +269 -0
- package/server/copilot-sdk-runtime.mjs +126 -0
- package/server/copilot-worker/copilot-attachments.mjs +97 -0
- package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
- package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
- package/server/copilot-worker/copilot-plan-board.mjs +110 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
- package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
- package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
- package/server/copilot-worker/fixtures/abort-turn.json +588 -0
- package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
- package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
- package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
- package/server/copilot-worker/fixtures/happy-turn.json +566 -0
- package/server/copilot-worker/fixtures/quota-turn.json +647 -0
- package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
- package/server/copilot-worker/fixtures/resume-turn.json +700 -0
- package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
- package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
- package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
- package/server/cursor-worker/cursor-attachments.mjs +88 -0
- package/server/cursor-worker/cursor-context-usage.mjs +85 -0
- package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
- package/server/cursor-worker/cursor-session-worker.mjs +145 -0
- package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
- package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
- package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
- package/server/db-schema.mjs +932 -0
- package/server/features.mjs +106 -0
- package/server/grok-worker/acp-client.mjs +434 -0
- package/server/grok-worker/acp-host-services.mjs +277 -0
- package/server/grok-worker/grok-context-usage.mjs +77 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
- package/server/grok-worker/grok-session-worker.mjs +145 -0
- package/server/grok-worker/grok-turn-runner.mjs +523 -0
- package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
- package/server/migrations/0001-add-structured-answer.mjs +80 -0
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
- package/server/migrations/0003-push-subscriptions.mjs +87 -0
- package/server/public/app/action-confirmations.js +267 -0
- package/server/public/app/activity-replay-state.mjs +149 -0
- package/server/public/app/api-client.js +1082 -0
- package/server/public/app/ask-user-view.js +545 -0
- package/server/public/app/attachments-view.js +2112 -0
- package/server/public/app/auto-compact-window-options.mjs +59 -0
- package/server/public/app/background-tasks-view.mjs +494 -0
- package/server/public/app/bootstrap.js +4347 -0
- package/server/public/app/claude-auth-ui.js +394 -0
- package/server/public/app/claude-thinking-options.mjs +38 -0
- package/server/public/app/cli-install-ui.js +455 -0
- package/server/public/app/code-copy.mjs +85 -0
- package/server/public/app/composer-attachment-cache.mjs +151 -0
- package/server/public/app/composer-control-state.mjs +107 -0
- package/server/public/app/composer-paste.mjs +132 -0
- package/server/public/app/context-tier-options.mjs +68 -0
- package/server/public/app/context-usage-view.mjs +338 -0
- package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
- package/server/public/app/conversation-preferences.mjs +91 -0
- package/server/public/app/conversation-provider-indicator.mjs +88 -0
- package/server/public/app/conversation-view.js +2898 -0
- package/server/public/app/cwd-menu-placement.mjs +85 -0
- package/server/public/app/cwd-picker.js +605 -0
- package/server/public/app/emoji-view.js +101 -0
- package/server/public/app/external-link-policy.mjs +80 -0
- package/server/public/app/file-preview-navigation.mjs +51 -0
- package/server/public/app/font-scaling.js +262 -0
- package/server/public/app/git-changes-view.js +339 -0
- package/server/public/app/git-diff-model.mjs +109 -0
- package/server/public/app/grok-auth-ui.js +387 -0
- package/server/public/app/image-downscale.mjs +174 -0
- package/server/public/app/in-flight-snapshot.mjs +40 -0
- package/server/public/app/infinite-loader.js +164 -0
- package/server/public/app/journal-view.js +1141 -0
- package/server/public/app/known-cwd-options.mjs +41 -0
- package/server/public/app/launch-cwd-path.mjs +23 -0
- package/server/public/app/live-message-dedupe.mjs +57 -0
- package/server/public/app/message-search-view.js +242 -0
- package/server/public/app/model-selector-options.mjs +31 -0
- package/server/public/app/model-vision-support.mjs +56 -0
- package/server/public/app/new-conversation-model-choice.mjs +67 -0
- package/server/public/app/plan-usage-view.mjs +307 -0
- package/server/public/app/preview-cards.mjs +273 -0
- package/server/public/app/preview-command.mjs +103 -0
- package/server/public/app/push-settings.js +370 -0
- package/server/public/app/pwa-install.js +456 -0
- package/server/public/app/question-schema-view.mjs +71 -0
- package/server/public/app/reasoning-effort-labels.mjs +42 -0
- package/server/public/app/relay-board-view.js +117 -0
- package/server/public/app/relay-dot-state.mjs +69 -0
- package/server/public/app/relay-error-ctas.mjs +52 -0
- package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
- package/server/public/app/relay-thoughts.mjs +98 -0
- package/server/public/app/relay-watchdog-policy.mjs +29 -0
- package/server/public/app/repo-browser-preferences.mjs +79 -0
- package/server/public/app/repo-browser-tree-state.mjs +97 -0
- package/server/public/app/router.js +657 -0
- package/server/public/app/selection-guard.mjs +93 -0
- package/server/public/app/settings-modal.js +1484 -0
- package/server/public/app/settings-tabs.js +129 -0
- package/server/public/app/slash-autocomplete.mjs +188 -0
- package/server/public/app/slash-commands.mjs +183 -0
- package/server/public/app/socket-handlers.js +703 -0
- package/server/public/app/status-store.mjs +242 -0
- package/server/public/app/status-view.mjs +315 -0
- package/server/public/app/store.js +1454 -0
- package/server/public/app/stream-state.mjs +73 -0
- package/server/public/app/streaming-dom-patch.mjs +23 -0
- package/server/public/app/sync-outbox.mjs +208 -0
- package/server/public/app/tap-actions.js +62 -0
- package/server/public/app/thread-order.mjs +54 -0
- package/server/public/app/tmux-inspector-view.js +422 -0
- package/server/public/app/transcript-separators.mjs +282 -0
- package/server/public/app-icon-192.png +0 -0
- package/server/public/app-icon-512.png +0 -0
- package/server/public/app-icon.svg +4 -0
- package/server/public/favicon.ico +0 -0
- package/server/public/index.html +5743 -0
- package/server/public/manifest.webmanifest +32 -0
- package/server/public/sw.js +307 -0
- package/server/relay-exit-codes.mjs +4 -0
- package/server/relay-self-restart.mjs +170 -0
- package/server/relay-tools.md +27 -0
- package/server/relay.mjs +1026 -0
- package/server/repositories/image-conversation-repository.mjs +317 -0
- package/server/repositories/message-repository.mjs +434 -0
- package/server/repositories/question-repository.mjs +161 -0
- package/server/repositories/session-repository.mjs +320 -0
- package/server/repositories/share-visibility-statements.mjs +24 -0
- package/server/routes/ask-user-routes.mjs +292 -0
- package/server/routes/cache-routes.mjs +44 -0
- package/server/routes/git-routes.mjs +92 -0
- package/server/routes/messages-routes-test-harness.mjs +111 -0
- package/server/routes/messages-routes.mjs +7107 -0
- package/server/routes/preview-routes.mjs +108 -0
- package/server/routes/push-routes.mjs +59 -0
- package/server/routes/relay-board-routes.mjs +268 -0
- package/server/routes/sessions-routes.mjs +6150 -0
- package/server/server-runtime.mjs +7463 -0
- package/server/server.js +24 -0
- package/server/services/ask-user-routing-service.mjs +117 -0
- package/server/services/cache-rebuild-service.mjs +236 -0
- package/server/services/claude-auth-service.mjs +578 -0
- package/server/services/claude-context-usage.mjs +240 -0
- package/server/services/claude-session-root-service.mjs +228 -0
- package/server/services/cli-install-service.mjs +1152 -0
- package/server/services/cli-process-runner.mjs +181 -0
- package/server/services/cloudflared-tunnel-service.mjs +358 -0
- package/server/services/context-snapshot-service.mjs +499 -0
- package/server/services/context-usage-view.mjs +160 -0
- package/server/services/conversation-preferences-service.mjs +208 -0
- package/server/services/cursor-dashboard-usage.mjs +217 -0
- package/server/services/cursor-session-root-service.mjs +62 -0
- package/server/services/delete-archive-service.mjs +154 -0
- package/server/services/drives-path-helpers.mjs +66 -0
- package/server/services/fixtures/claude-auth-stub.sh +62 -0
- package/server/services/fixtures/cli-install-stub.sh +75 -0
- package/server/services/fixtures/grok-stub.sh +86 -0
- package/server/services/generated-image-cleanup-service.mjs +114 -0
- package/server/services/git-changes-service.mjs +195 -0
- package/server/services/github-billing-usage.mjs +133 -0
- package/server/services/grok-auth-service.mjs +593 -0
- package/server/services/grok-billing-usage.mjs +120 -0
- package/server/services/image-context-service.mjs +106 -0
- package/server/services/image-operation-service.mjs +135 -0
- package/server/services/keyed-mutex-service.mjs +82 -0
- package/server/services/mime-sniffer.mjs +118 -0
- package/server/services/oar-cli-helpers.mjs +109 -0
- package/server/services/oar-state-migration-service.mjs +243 -0
- package/server/services/plan-usage-claude.mjs +387 -0
- package/server/services/plan-usage-contract.mjs +271 -0
- package/server/services/plan-usage-copilot.mjs +421 -0
- package/server/services/plan-usage-cursor.mjs +566 -0
- package/server/services/plan-usage-grok.mjs +436 -0
- package/server/services/plan-usage-service.mjs +487 -0
- package/server/services/preview-health-probe.mjs +82 -0
- package/server/services/preview-proxy-server.mjs +498 -0
- package/server/services/preview-registry-service.mjs +457 -0
- package/server/services/preview-static-handler.mjs +237 -0
- package/server/services/provider-model-selection.mjs +91 -0
- package/server/services/provider-reasoning-effort.mjs +94 -0
- package/server/services/push-active-device-service.mjs +57 -0
- package/server/services/push-dispatch-service.mjs +459 -0
- package/server/services/rate-limit-service.mjs +63 -0
- package/server/services/relay-bridge-owner-service.mjs +84 -0
- package/server/services/relay-cli-launcher-service.mjs +209 -0
- package/server/services/relay-prompt-sanitizer.mjs +137 -0
- package/server/services/relay-queue-gate-service.mjs +38 -0
- package/server/services/relay-restart-orchestrator-service.mjs +935 -0
- package/server/services/relay-singleton-guard.mjs +124 -0
- package/server/services/safe-served-content.mjs +57 -0
- package/server/services/sdk-session-import-service.mjs +271 -0
- package/server/services/sdk-session-sync-service.mjs +207 -0
- package/server/services/session-history-refresh-service.mjs +497 -0
- package/server/services/session-transcript-service.mjs +419 -0
- package/server/services/session-worker-launch-service.mjs +786 -0
- package/server/services/session-worker-process-service.mjs +319 -0
- package/server/services/session-worker-registry-service.mjs +194 -0
- package/server/services/session-worker-stop-service.mjs +141 -0
- package/server/services/session-worker-supervisor-service.mjs +898 -0
- package/server/services/session-worker-websocket-service.mjs +483 -0
- package/server/services/ssh-tunnel-manager-service.mjs +423 -0
- package/server/services/status-event-service.mjs +158 -0
- package/server/services/tmux-inspector-access-policy.mjs +77 -0
- package/server/services/tmux-inspector-socket-service.mjs +191 -0
- package/server/services/tmux-inspector-stream-service.mjs +552 -0
- package/server/services/tunnel-worker-path-guard.mjs +107 -0
- package/server/services/upload-sweep.mjs +60 -0
- package/server/services/usage-snapshot-helpers.mjs +209 -0
- package/server/services/windows-autostart-service.mjs +115 -0
- package/server/services/workspace-root-defaults-service.mjs +34 -0
- package/server/services/workspace-root-path-policy.mjs +196 -0
- package/server/services/workspace-root-relaunch-service.mjs +132 -0
- package/server/services/workspace-symlink-guard.mjs +54 -0
- package/server/tools/capture-proxy.mjs +173 -0
- package/server/tty-console-bootstrap.mjs +75 -0
- package/server/uploads/README.md +36 -0
- package/server/uploads/image_input.md +540 -0
- package/server/windows-terminal-launcher.mjs +29 -0
- package/server/workspace-root.mjs +87 -0
- package/shared/ask-user-bridge.mjs +131 -0
- package/shared/auto-compact-window.mjs +116 -0
- package/shared/background-task-timeout.mjs +52 -0
- package/shared/claude-thinking.mjs +101 -0
- package/shared/context-window-fallbacks.mjs +148 -0
- package/shared/control-poller.mjs +115 -0
- package/shared/empty-turn-completion.mjs +20 -0
- package/shared/model-catalog-freshness.mjs +11 -0
- package/shared/model-descriptors.mjs +118 -0
- package/shared/model-id.mjs +147 -0
- package/shared/model-refresh.mjs +24 -0
- package/shared/openai-reasoning.mjs +13 -0
- package/shared/plan-lines.mjs +13 -0
- package/shared/preview-instructions.mjs +77 -0
- package/shared/preview-tool-core.mjs +191 -0
- package/shared/provider-routing.mjs +29 -0
- package/shared/question-schema.mjs +315 -0
- package/shared/question-timeout.mjs +18 -0
- package/shared/stream-emit-gating.mjs +28 -0
- package/shared/subagent-run-id.mjs +17 -0
- package/shared/thought-cap.mjs +13 -0
- package/shared/tool-activity.mjs +98 -0
- package/shared/turn-ceiling.mjs +53 -0
- package/shared/worker-bootstrap.mjs +44 -0
- package/shared/worker-crash-guard.mjs +60 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import { extractToolName, parseMaybeJson, toolArgsSnapshot } from "./tool-activity.mjs";
|
|
2
|
+
import {
|
|
3
|
+
extractRequestedSchema,
|
|
4
|
+
schemaFields,
|
|
5
|
+
validateStructuredAnswer,
|
|
6
|
+
flatAnswerToStructured,
|
|
7
|
+
summarizeStructuredAnswer,
|
|
8
|
+
} from "../../../../shared/question-schema.mjs";
|
|
9
|
+
|
|
10
|
+
function extractSubagentRunIdFromRequest(request) {
|
|
11
|
+
const agentId = request?.agentId || request?.data?.agentId || null;
|
|
12
|
+
const id = agentId ? String(agentId).trim() : "";
|
|
13
|
+
return id || null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isReportIntentTool(request) {
|
|
17
|
+
const name = extractToolName(request).toLowerCase();
|
|
18
|
+
return name.includes("report_intent") || name === "report_intent";
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function extractReportIntentText(request) {
|
|
22
|
+
const args = toolArgsSnapshot(request);
|
|
23
|
+
const candidates = [
|
|
24
|
+
args?.intent,
|
|
25
|
+
args?.description,
|
|
26
|
+
args?.message,
|
|
27
|
+
args?.text,
|
|
28
|
+
args?.content,
|
|
29
|
+
args?.summary,
|
|
30
|
+
args?.thought,
|
|
31
|
+
args?.reasoning,
|
|
32
|
+
];
|
|
33
|
+
for (const value of candidates) {
|
|
34
|
+
const text = String(value || "").trim();
|
|
35
|
+
if (text) return text;
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createQuestionRoutingHooks({
|
|
41
|
+
api,
|
|
42
|
+
dbg,
|
|
43
|
+
forwardRelayQuestion,
|
|
44
|
+
isAskUserTool,
|
|
45
|
+
normalizeActivityText,
|
|
46
|
+
formatToolActivity,
|
|
47
|
+
formatToolResultActivity,
|
|
48
|
+
extractQuestionChoices,
|
|
49
|
+
maxToolDetailLength = 140,
|
|
50
|
+
getRelayTurnActive,
|
|
51
|
+
getActiveMessage,
|
|
52
|
+
setLastAskUserBridge,
|
|
53
|
+
getLastActivityText,
|
|
54
|
+
setLastActivityText,
|
|
55
|
+
setPendingAskUserRequest,
|
|
56
|
+
}) {
|
|
57
|
+
const allowToolUse = { permissionDecision: "allow" };
|
|
58
|
+
|
|
59
|
+
function normalizePlanBoardActions(rawActions, recommendedAction = "") {
|
|
60
|
+
const ids = Array.isArray(rawActions)
|
|
61
|
+
? rawActions
|
|
62
|
+
.map((entry) => {
|
|
63
|
+
if (typeof entry === "string") return entry.trim().toLowerCase();
|
|
64
|
+
if (entry && typeof entry === "object") {
|
|
65
|
+
return String(entry.id || entry.actionId || entry.value || "").trim().toLowerCase();
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
})
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
: [];
|
|
71
|
+
const sourceIds = ids.length ? ids : ["autopilot", "interactive", "exit_only"];
|
|
72
|
+
const seen = new Set();
|
|
73
|
+
const deduped = [];
|
|
74
|
+
for (const id of sourceIds) {
|
|
75
|
+
if (seen.has(id)) continue;
|
|
76
|
+
seen.add(id);
|
|
77
|
+
deduped.push(id);
|
|
78
|
+
}
|
|
79
|
+
const recommended = String(recommendedAction || "").trim().toLowerCase();
|
|
80
|
+
return {
|
|
81
|
+
actions: deduped.map((id) => {
|
|
82
|
+
if (id === "autopilot_fleet") return { id, label: "Implement with autopilot fleet", mode: "autopilot" };
|
|
83
|
+
if (id === "autopilot") return { id, label: "Implement in autopilot", mode: "autopilot" };
|
|
84
|
+
if (id === "interactive") return { id, label: "Stop here and prompt myself", mode: "agent" };
|
|
85
|
+
if (id === "exit_only") return { id, label: "Stop here", mode: "agent" };
|
|
86
|
+
return { id, label: id.replace(/[_-]+/g, " "), mode: null };
|
|
87
|
+
}),
|
|
88
|
+
recommendedAction: recommended || null,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function firstNonEmptyPlanField(...values) {
|
|
93
|
+
for (const value of values) {
|
|
94
|
+
const text = String(value || "").trim();
|
|
95
|
+
if (text) return text;
|
|
96
|
+
}
|
|
97
|
+
return "";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractPlanBoardArgs(request) {
|
|
101
|
+
const parsedBody = parseMaybeJson(request?.body);
|
|
102
|
+
const parsedPayload = parseMaybeJson(request?.payload);
|
|
103
|
+
const parsedRequest = parseMaybeJson(request?.request);
|
|
104
|
+
const parsedToolCall = parseMaybeJson(request?.toolCall);
|
|
105
|
+
const rawCandidates = [
|
|
106
|
+
toolArgsSnapshot(request),
|
|
107
|
+
parseMaybeJson(request?.input),
|
|
108
|
+
parseMaybeJson(request?.arguments),
|
|
109
|
+
parseMaybeJson(request?.args),
|
|
110
|
+
parseMaybeJson(request?.toolArgs),
|
|
111
|
+
parseMaybeJson(request?.toolInput),
|
|
112
|
+
parsedBody,
|
|
113
|
+
parsedPayload,
|
|
114
|
+
parsedRequest,
|
|
115
|
+
parsedToolCall,
|
|
116
|
+
toolArgsSnapshot(parsedBody),
|
|
117
|
+
toolArgsSnapshot(parsedPayload),
|
|
118
|
+
toolArgsSnapshot(parsedRequest),
|
|
119
|
+
toolArgsSnapshot(parsedToolCall),
|
|
120
|
+
parseMaybeJson(parsedBody?.input),
|
|
121
|
+
parseMaybeJson(parsedBody?.arguments),
|
|
122
|
+
parseMaybeJson(parsedPayload?.input),
|
|
123
|
+
parseMaybeJson(parsedPayload?.arguments),
|
|
124
|
+
parseMaybeJson(parsedRequest?.input),
|
|
125
|
+
parseMaybeJson(parsedRequest?.arguments),
|
|
126
|
+
parseMaybeJson(parsedToolCall?.input),
|
|
127
|
+
parseMaybeJson(parsedToolCall?.arguments),
|
|
128
|
+
];
|
|
129
|
+
const seen = new Set();
|
|
130
|
+
const candidates = rawCandidates.filter((value) => {
|
|
131
|
+
if (!value || typeof value !== "object") return false;
|
|
132
|
+
if (seen.has(value)) return false;
|
|
133
|
+
seen.add(value);
|
|
134
|
+
return true;
|
|
135
|
+
});
|
|
136
|
+
for (const candidate of candidates) {
|
|
137
|
+
if (firstNonEmptyPlanField(candidate.summary, candidate.result, candidate.output, candidate.message)) {
|
|
138
|
+
return candidate;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return candidates[0] || null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildPlanBoardPayloadFromRequest(request, activeMsg) {
|
|
145
|
+
const toolName = extractToolName(request).toLowerCase();
|
|
146
|
+
const relayMode = String(activeMsg?.relayMode || "").trim().toLowerCase();
|
|
147
|
+
if (!activeMsg?.id) return null;
|
|
148
|
+
if (toolName !== "exit_plan_mode" && !(toolName === "task_complete" && relayMode === "plan")) return null;
|
|
149
|
+
|
|
150
|
+
const args = extractPlanBoardArgs(request);
|
|
151
|
+
if (!args || typeof args !== "object") return null;
|
|
152
|
+
|
|
153
|
+
const summary = firstNonEmptyPlanField(args.summary, args.result, args.output, args.message);
|
|
154
|
+
if (!summary) return null;
|
|
155
|
+
|
|
156
|
+
const normalized = normalizePlanBoardActions(args.actions, args.recommendedAction);
|
|
157
|
+
return {
|
|
158
|
+
queueId: activeMsg.id,
|
|
159
|
+
messageId: activeMsg.id,
|
|
160
|
+
conversationId: activeMsg.conversationId,
|
|
161
|
+
mode: activeMsg.relayMode || "agent",
|
|
162
|
+
boardType: "plan_ready",
|
|
163
|
+
title: "Plan ready for review",
|
|
164
|
+
body: summary,
|
|
165
|
+
actions: normalized.actions,
|
|
166
|
+
recommendedAction: normalized.recommendedAction,
|
|
167
|
+
context: {
|
|
168
|
+
source: toolName,
|
|
169
|
+
queueMessageId: activeMsg.id || null,
|
|
170
|
+
conversationId: activeMsg.conversationId || null,
|
|
171
|
+
relayMode: activeMsg.relayMode || "agent",
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function answerActivityText(answer) {
|
|
177
|
+
const normalized = normalizeActivityText(answer, maxToolDetailLength) || String(answer || "").trim();
|
|
178
|
+
const escaped = normalized.replace(/"/g, "'");
|
|
179
|
+
return {
|
|
180
|
+
normalized,
|
|
181
|
+
text: normalized
|
|
182
|
+
? `Tool (ask_user): user answered "${escaped}"`
|
|
183
|
+
: "Tool (ask_user): answered via web relay",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function timeoutActivityText() {
|
|
188
|
+
return {
|
|
189
|
+
normalized: "",
|
|
190
|
+
text: "Tool (ask_user): user did not respond before timeout; continuing according to relay mode",
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function onPreToolUse(request) {
|
|
195
|
+
if (!getRelayTurnActive()) {
|
|
196
|
+
return allowToolUse;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const activeMsg = getActiveMessage();
|
|
200
|
+
const planBoardPayload = buildPlanBoardPayloadFromRequest(request, activeMsg);
|
|
201
|
+
if (planBoardPayload) {
|
|
202
|
+
await api("POST", "/api/relay-board", planBoardPayload).catch((error) => {
|
|
203
|
+
dbg("plan board publish failed", `msgId=${activeMsg.id}`, error?.message || String(error));
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (isAskUserTool(request) && activeMsg?.id) {
|
|
208
|
+
const extractedChoices = extractQuestionChoices(request);
|
|
209
|
+
dbg("onPreToolUse: detected ask_user tool", `msgId=${activeMsg.id}`, "waiting for onUserInputRequest callback...");
|
|
210
|
+
dbg("onPreToolUse: ask_user request keys=", Object.keys(request || {}).join(","), "toolArgs=", JSON.stringify(request?.toolArgs)?.slice(0, 300), "choices=", JSON.stringify(extractedChoices));
|
|
211
|
+
setLastAskUserBridge(null);
|
|
212
|
+
setPendingAskUserRequest?.(null);
|
|
213
|
+
dbg("onPreToolUse: ask_user fallback staging disabled; waiting for SDK callbacks", `msgId=${activeMsg.id}`);
|
|
214
|
+
await api("POST", "/api/activity", {
|
|
215
|
+
messageId: activeMsg.id,
|
|
216
|
+
conversationId: activeMsg.conversationId,
|
|
217
|
+
mode: activeMsg.relayMode || "agent",
|
|
218
|
+
text: "Tool (ask_user): clarification requested in web relay",
|
|
219
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
220
|
+
}).catch(() => {});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const reportIntentTool = isReportIntentTool(request);
|
|
224
|
+
const activityText = formatToolActivity(request, maxToolDetailLength);
|
|
225
|
+
if (!reportIntentTool && activityText && activeMsg?.id && activityText !== getLastActivityText()) {
|
|
226
|
+
setLastActivityText(activityText);
|
|
227
|
+
await api("POST", "/api/activity", {
|
|
228
|
+
messageId: activeMsg.id,
|
|
229
|
+
conversationId: activeMsg.conversationId,
|
|
230
|
+
mode: activeMsg.relayMode || "agent",
|
|
231
|
+
text: activityText,
|
|
232
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
233
|
+
}).catch(() => {});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Route report_intent full text through thought pipeline (avoids 140-char activity truncation)
|
|
237
|
+
if (reportIntentTool && activeMsg?.id) {
|
|
238
|
+
const fullIntentText = extractReportIntentText(request);
|
|
239
|
+
if (fullIntentText) {
|
|
240
|
+
await api("POST", "/api/thought", {
|
|
241
|
+
messageId: activeMsg.id,
|
|
242
|
+
conversationId: activeMsg.conversationId,
|
|
243
|
+
mode: activeMsg.relayMode || "agent",
|
|
244
|
+
reasoningId: `intent-${Date.now()}`,
|
|
245
|
+
text: fullIntentText,
|
|
246
|
+
done: true,
|
|
247
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
248
|
+
}).catch((error) => {
|
|
249
|
+
dbg("report_intent thought publish failed", `msgId=${activeMsg.id}`, error?.message || String(error));
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return allowToolUse;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async function onPostToolUse(request, result) {
|
|
258
|
+
if (!getRelayTurnActive()) return;
|
|
259
|
+
const activeMsg = getActiveMessage();
|
|
260
|
+
if (!activeMsg?.id) return;
|
|
261
|
+
const activityText = formatToolResultActivity(request, result, maxToolDetailLength);
|
|
262
|
+
if (!activityText || activityText === getLastActivityText()) return;
|
|
263
|
+
setLastActivityText(activityText);
|
|
264
|
+
await api("POST", "/api/activity", {
|
|
265
|
+
messageId: activeMsg.id,
|
|
266
|
+
conversationId: activeMsg.conversationId,
|
|
267
|
+
mode: activeMsg.relayMode || "agent",
|
|
268
|
+
text: activityText,
|
|
269
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
270
|
+
}).catch((error) => {
|
|
271
|
+
dbg("tool result activity publish failed", `msgId=${activeMsg.id}`, error?.message || String(error));
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function onUserInputRequest(request) {
|
|
276
|
+
const activeMsg = getActiveMessage();
|
|
277
|
+
if (!getRelayTurnActive() || !activeMsg?.id) {
|
|
278
|
+
dbg(
|
|
279
|
+
"ask_user bridge skipped: no active relay turn",
|
|
280
|
+
`relayTurnActive=${String(getRelayTurnActive())}`,
|
|
281
|
+
`hasActiveMessage=${String(!!activeMsg?.id)}`,
|
|
282
|
+
);
|
|
283
|
+
// Throw so the CLI runtime knows input is unavailable.
|
|
284
|
+
// With onUserInputRequest registered at top level (requestUserInput: true), the CLI will not
|
|
285
|
+
// show a terminal prompt for relay turns — this error only fires for non-relay CLI turns.
|
|
286
|
+
throw new Error("ask_user: no active relay turn — user input unavailable outside web relay context.");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
dbg(
|
|
290
|
+
"forwarding user input request for msgId",
|
|
291
|
+
activeMsg.id,
|
|
292
|
+
"mode",
|
|
293
|
+
activeMsg.relayMode || "agent",
|
|
294
|
+
"keys",
|
|
295
|
+
Object.keys(request || {}).join(","),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
await api("POST", "/api/activity", {
|
|
299
|
+
messageId: activeMsg.id,
|
|
300
|
+
conversationId: activeMsg.conversationId,
|
|
301
|
+
mode: activeMsg.relayMode || "agent",
|
|
302
|
+
text: "Tool (ask_user): question posted; waiting for user answer",
|
|
303
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
304
|
+
}).catch(() => {});
|
|
305
|
+
|
|
306
|
+
setPendingAskUserRequest?.(null); // Normal bridge is handling it — clear so autopilot path is skipped
|
|
307
|
+
const result = await forwardRelayQuestion(request);
|
|
308
|
+
const answer = String(result?.answer || "");
|
|
309
|
+
setLastAskUserBridge({
|
|
310
|
+
source: "onUserInputRequest",
|
|
311
|
+
at: Date.now(),
|
|
312
|
+
messageId: activeMsg.id,
|
|
313
|
+
});
|
|
314
|
+
const activity = result?.timedOut ? timeoutActivityText() : answerActivityText(answer);
|
|
315
|
+
await api("POST", "/api/activity", {
|
|
316
|
+
messageId: activeMsg.id,
|
|
317
|
+
conversationId: activeMsg.conversationId,
|
|
318
|
+
mode: activeMsg.relayMode || "agent",
|
|
319
|
+
text: activity.text,
|
|
320
|
+
subagentRunId: extractSubagentRunIdFromRequest(request) || undefined,
|
|
321
|
+
}).catch(() => {});
|
|
322
|
+
|
|
323
|
+
dbg(
|
|
324
|
+
"relay question answered for msgId",
|
|
325
|
+
activeMsg.id,
|
|
326
|
+
"answerLen",
|
|
327
|
+
String(answer.length),
|
|
328
|
+
`answer="${activity.normalized}"`,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
answer,
|
|
333
|
+
wasFreeform: !extractQuestionChoices(request).length,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function buildDefaultsContent(schema) {
|
|
338
|
+
const fields = schemaFields(schema);
|
|
339
|
+
if (!fields.length) return null;
|
|
340
|
+
const content = {};
|
|
341
|
+
for (const field of fields) {
|
|
342
|
+
if (field.hasDefault) {
|
|
343
|
+
content[field.name] = field.default;
|
|
344
|
+
} else if (field.required) {
|
|
345
|
+
// A required field has no default — cannot synthesize a complete answer.
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return content;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function buildElicitationContent(schema, result) {
|
|
353
|
+
// Prefer the validated structured answer captured by the relay UI.
|
|
354
|
+
if (result?.structuredAnswer && typeof result.structuredAnswer === "object") {
|
|
355
|
+
const validation = validateStructuredAnswer(schema, result.structuredAnswer);
|
|
356
|
+
return validation.ok ? validation.value : result.structuredAnswer;
|
|
357
|
+
}
|
|
358
|
+
// Fall back to mapping a flat string answer onto a single-field schema.
|
|
359
|
+
const flat = String(result?.answer || "").trim();
|
|
360
|
+
if (flat) {
|
|
361
|
+
const mapped = flatAnswerToStructured(schema, flat);
|
|
362
|
+
if (mapped) return mapped;
|
|
363
|
+
const fields = schemaFields(schema);
|
|
364
|
+
if (fields.length === 1) return { [fields[0].name]: flat };
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
async function onElicitationRequest(request) {
|
|
370
|
+
const activeMsg = getActiveMessage();
|
|
371
|
+
if (!getRelayTurnActive() || !activeMsg?.id) {
|
|
372
|
+
dbg(
|
|
373
|
+
"elicitation bridge skipped: no active relay turn",
|
|
374
|
+
`relayTurnActive=${String(getRelayTurnActive())}`,
|
|
375
|
+
`hasActiveMessage=${String(!!activeMsg?.id)}`,
|
|
376
|
+
);
|
|
377
|
+
throw new Error("ask_user: no active relay turn — user input unavailable outside web relay context.");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const schema = extractRequestedSchema(request);
|
|
381
|
+
const fieldCount = schema ? schemaFields(schema).length : 0;
|
|
382
|
+
dbg(
|
|
383
|
+
"forwarding elicitation request for msgId",
|
|
384
|
+
activeMsg.id,
|
|
385
|
+
"mode",
|
|
386
|
+
activeMsg.relayMode || "agent",
|
|
387
|
+
"fields",
|
|
388
|
+
String(fieldCount),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
await api("POST", "/api/activity", {
|
|
392
|
+
messageId: activeMsg.id,
|
|
393
|
+
conversationId: activeMsg.conversationId,
|
|
394
|
+
mode: activeMsg.relayMode || "agent",
|
|
395
|
+
text: fieldCount > 1
|
|
396
|
+
? `Tool (ask_user): ${fieldCount}-field form posted; waiting for user answer`
|
|
397
|
+
: "Tool (ask_user): question posted; waiting for user answer",
|
|
398
|
+
}).catch(() => {});
|
|
399
|
+
|
|
400
|
+
setPendingAskUserRequest?.(null);
|
|
401
|
+
const result = await forwardRelayQuestion(request);
|
|
402
|
+
setLastAskUserBridge({
|
|
403
|
+
source: "onElicitationRequest",
|
|
404
|
+
at: Date.now(),
|
|
405
|
+
messageId: activeMsg.id,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
if (result?.timedOut) {
|
|
409
|
+
const defaults = buildDefaultsContent(schema);
|
|
410
|
+
const activity = timeoutActivityText();
|
|
411
|
+
await api("POST", "/api/activity", {
|
|
412
|
+
messageId: activeMsg.id,
|
|
413
|
+
conversationId: activeMsg.conversationId,
|
|
414
|
+
mode: activeMsg.relayMode || "agent",
|
|
415
|
+
text: activity.text,
|
|
416
|
+
}).catch(() => {});
|
|
417
|
+
if (defaults) {
|
|
418
|
+
dbg("elicitation timed out for msgId", activeMsg.id, "— accepting schema defaults");
|
|
419
|
+
return { action: "accept", content: defaults };
|
|
420
|
+
}
|
|
421
|
+
dbg("elicitation timed out for msgId", activeMsg.id, "— declining");
|
|
422
|
+
return { action: "decline" };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const content = buildElicitationContent(schema, result);
|
|
426
|
+
if (!content) {
|
|
427
|
+
dbg("elicitation produced no usable content for msgId", activeMsg.id, "— declining");
|
|
428
|
+
await api("POST", "/api/activity", {
|
|
429
|
+
messageId: activeMsg.id,
|
|
430
|
+
conversationId: activeMsg.conversationId,
|
|
431
|
+
mode: activeMsg.relayMode || "agent",
|
|
432
|
+
text: "Tool (ask_user): no answer captured; continuing according to relay mode",
|
|
433
|
+
}).catch(() => {});
|
|
434
|
+
return { action: "decline" };
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const summary = summarizeStructuredAnswer(schema, content) || String(result?.answer || "");
|
|
438
|
+
const escaped = summary.replace(/"/g, "'");
|
|
439
|
+
await api("POST", "/api/activity", {
|
|
440
|
+
messageId: activeMsg.id,
|
|
441
|
+
conversationId: activeMsg.conversationId,
|
|
442
|
+
mode: activeMsg.relayMode || "agent",
|
|
443
|
+
text: summary ? `Tool (ask_user): user answered "${escaped}"` : "Tool (ask_user): answered via web relay",
|
|
444
|
+
}).catch(() => {});
|
|
445
|
+
|
|
446
|
+
dbg(
|
|
447
|
+
"elicitation answered for msgId",
|
|
448
|
+
activeMsg.id,
|
|
449
|
+
"fields",
|
|
450
|
+
String(Object.keys(content).length),
|
|
451
|
+
`summary="${summary.slice(0, 120)}"`,
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
return { action: "accept", content };
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return {
|
|
458
|
+
onPreToolUse,
|
|
459
|
+
onPostToolUse,
|
|
460
|
+
onUserInputRequest,
|
|
461
|
+
onElicitationRequest,
|
|
462
|
+
};
|
|
463
|
+
}
|