@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,216 @@
|
|
|
1
|
+
// Bridges the Copilot runtime's two blocking interactive surfaces onto the
|
|
2
|
+
// relay's question cards:
|
|
3
|
+
//
|
|
4
|
+
// * `onUserInputRequest` — the model called `ask_user`. Phase 1 answered these
|
|
5
|
+
// in-band with a "not supported" note; this asks the human.
|
|
6
|
+
// * `onPermissionRequest` in **ask** mode — the model wants to run a mutating
|
|
7
|
+
// tool. Phase 1 blanket-rejected; this offers approve/deny.
|
|
8
|
+
//
|
|
9
|
+
// Both are wired to the SAME relay endpoints the extension's
|
|
10
|
+
// `skills/question-bridge.mjs` uses, so a question raised by the SDK engine
|
|
11
|
+
// renders as the identical card, answers through the identical route, and
|
|
12
|
+
// honours the identical timeout as one raised by the extension engine.
|
|
13
|
+
//
|
|
14
|
+
// The polling/timeout/abort loop is NOT re-implemented here: it is
|
|
15
|
+
// `createAskUserBridge(...).waitForRelayQuestionAnswer` from
|
|
16
|
+
// `shared/ask-user-bridge.mjs`, reused verbatim. Only the CREATE payload is
|
|
17
|
+
// local, because Copilot's request shape carries things the shared
|
|
18
|
+
// `handleAskUserQuestion` cannot express — a real `allowFreeform` (the shared
|
|
19
|
+
// helper hardcodes `true`) and a single question rather than a Claude-style
|
|
20
|
+
// question array. Composing the waiter instead of copying it keeps one
|
|
21
|
+
// implementation of the abort semantics.
|
|
22
|
+
import { createAskUserBridge } from '../../shared/ask-user-bridge.mjs';
|
|
23
|
+
import {
|
|
24
|
+
DEFAULT_QUESTION_TIMEOUT_MS,
|
|
25
|
+
QUESTION_TIMEOUT_CONTINUATION_TEXT,
|
|
26
|
+
} from '../../shared/question-timeout.mjs';
|
|
27
|
+
import { describePermissionRequest } from './copilot-sdk-adapter.mjs';
|
|
28
|
+
|
|
29
|
+
/** Choice labels for a permission card. Freeform text is treated as denial. */
|
|
30
|
+
export const PERMISSION_APPROVE_CHOICE = 'Approve';
|
|
31
|
+
export const PERMISSION_DENY_CHOICE = 'Deny';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Normalize the runtime's `choices` into a string array.
|
|
35
|
+
*
|
|
36
|
+
* `choices` is documented as nullable — `null` means "no choices, free text
|
|
37
|
+
* only" and must NOT become `[]`-with-`allowFreeform:false`, which would render
|
|
38
|
+
* a card the user cannot answer at all.
|
|
39
|
+
*/
|
|
40
|
+
export function normalizeUserInputChoices(choices) {
|
|
41
|
+
if (!Array.isArray(choices)) return [];
|
|
42
|
+
return choices
|
|
43
|
+
.map((choice) => (typeof choice === 'string' ? choice : String(choice?.label ?? choice?.value ?? '')))
|
|
44
|
+
.map((choice) => choice.trim())
|
|
45
|
+
.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Did the human type their own answer rather than pick an offered choice?
|
|
50
|
+
*
|
|
51
|
+
* The relay wire path is identical for both (the UI posts the chosen label as
|
|
52
|
+
* `answer`), so this is derived rather than reported. An answer that exactly
|
|
53
|
+
* matches an offered choice is a selection; anything else — including the
|
|
54
|
+
* timeout continuation text — is freeform. `wasFreeform` is REQUIRED by
|
|
55
|
+
* `UserInputResponse` and the runtime's deserializer is strict, so it must
|
|
56
|
+
* always be a real boolean.
|
|
57
|
+
*/
|
|
58
|
+
export function deriveWasFreeform(answer, choices) {
|
|
59
|
+
const normalized = normalizeUserInputChoices(choices);
|
|
60
|
+
if (!normalized.length) return true;
|
|
61
|
+
const value = String(answer ?? '').trim();
|
|
62
|
+
return !normalized.some((choice) => choice === value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createCopilotQuestionBridge({
|
|
66
|
+
api,
|
|
67
|
+
sdkSessionId = '',
|
|
68
|
+
getActiveMessage,
|
|
69
|
+
questionPollMs = 1500,
|
|
70
|
+
questionTimeoutMs = DEFAULT_QUESTION_TIMEOUT_MS,
|
|
71
|
+
sleep,
|
|
72
|
+
dbg = () => {},
|
|
73
|
+
} = {}) {
|
|
74
|
+
const shared = createAskUserBridge({
|
|
75
|
+
api,
|
|
76
|
+
sdkSessionId,
|
|
77
|
+
getActiveMessage,
|
|
78
|
+
questionPollMs,
|
|
79
|
+
questionTimeoutMs,
|
|
80
|
+
...(sleep ? { sleep } : {}),
|
|
81
|
+
dbg,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Question ids this worker created and has not yet seen settle. Shutdown
|
|
85
|
+
// walks these so a pending card is timed out deliberately rather than left
|
|
86
|
+
// spinning in the UI until the relay's 10s expiry sweeper notices.
|
|
87
|
+
const pendingQuestionIds = new Set();
|
|
88
|
+
|
|
89
|
+
async function createQuestion({ prompt, choices, allowFreeform, source, rationale, extra = {} }) {
|
|
90
|
+
const activeMsg = typeof getActiveMessage === 'function' ? getActiveMessage() : null;
|
|
91
|
+
const payload = {
|
|
92
|
+
// The relay 409s ("No active relay turn") unless this queue row is
|
|
93
|
+
// `processing`, which is exactly the state a blocking handler runs in.
|
|
94
|
+
queueId: activeMsg?.id,
|
|
95
|
+
messageId: activeMsg?.id,
|
|
96
|
+
conversationId: activeMsg?.conversationId,
|
|
97
|
+
mode: activeMsg?.relayMode || 'agent',
|
|
98
|
+
prompt,
|
|
99
|
+
choices,
|
|
100
|
+
allowFreeform,
|
|
101
|
+
sdk_session_id: sdkSessionId || undefined,
|
|
102
|
+
timeout_ms: questionTimeoutMs,
|
|
103
|
+
context: {
|
|
104
|
+
source,
|
|
105
|
+
rationale,
|
|
106
|
+
queueMessageId: activeMsg?.id || null,
|
|
107
|
+
conversationId: activeMsg?.conversationId || null,
|
|
108
|
+
relayMode: activeMsg?.relayMode || 'agent',
|
|
109
|
+
...extra,
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const created = await api('POST', '/api/relay-question', payload);
|
|
113
|
+
const questionId = created?.question?.id;
|
|
114
|
+
if (!questionId) throw new Error('Relay question could not be created');
|
|
115
|
+
return questionId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function ask(spec, { signal } = {}) {
|
|
119
|
+
const questionId = await createQuestion(spec);
|
|
120
|
+
pendingQuestionIds.add(questionId);
|
|
121
|
+
dbg('relay question created', questionId, spec.source, spec.prompt.slice(0, 80));
|
|
122
|
+
try {
|
|
123
|
+
return await shared.waitForRelayQuestionAnswer(questionId, { signal });
|
|
124
|
+
} finally {
|
|
125
|
+
pendingQuestionIds.delete(questionId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* `onUserInputRequest` → a relay question card → `{ answer, wasFreeform }`.
|
|
131
|
+
*
|
|
132
|
+
* Never throws: a handler that throws is auto-answered `user-not-available`
|
|
133
|
+
* by the SDK for permissions, and for user input it would fail the tool call
|
|
134
|
+
* silently. A relay that is unreachable therefore degrades to the same
|
|
135
|
+
* in-band note phase 1 always returned, which lets the model continue.
|
|
136
|
+
*/
|
|
137
|
+
async function askUserInput(request, { signal } = {}) {
|
|
138
|
+
const question = String(request?.question || '').trim();
|
|
139
|
+
const choices = normalizeUserInputChoices(request?.choices);
|
|
140
|
+
// `allowFreeform` is only meaningful alongside choices; with none, the card
|
|
141
|
+
// must accept free text or it cannot be answered.
|
|
142
|
+
const allowFreeform = choices.length ? request?.allowFreeform !== false : true;
|
|
143
|
+
const result = await ask({
|
|
144
|
+
prompt: question || 'Copilot asked for input to continue this turn.',
|
|
145
|
+
choices,
|
|
146
|
+
allowFreeform,
|
|
147
|
+
source: 'onUserInputRequest',
|
|
148
|
+
rationale: 'Copilot requested clarification to continue this turn.',
|
|
149
|
+
extra: { requestId: String(request?.requestId || '') || undefined },
|
|
150
|
+
}, { signal });
|
|
151
|
+
const answer = String(result?.answer ?? '');
|
|
152
|
+
return {
|
|
153
|
+
answer,
|
|
154
|
+
wasFreeform: result?.timedOut ? true : deriveWasFreeform(answer, choices),
|
|
155
|
+
timedOut: result?.timedOut === true,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* `onPermissionRequest` in ask mode → an approve/deny card.
|
|
161
|
+
*
|
|
162
|
+
* Freeform is allowed and anything that is not exactly "Approve" denies —
|
|
163
|
+
* so a human can type *why* they are refusing and the model receives it as
|
|
164
|
+
* feedback instead of a bare refusal.
|
|
165
|
+
*/
|
|
166
|
+
async function askToolApproval(request, { signal } = {}) {
|
|
167
|
+
const description = describePermissionRequest(request);
|
|
168
|
+
const result = await ask({
|
|
169
|
+
prompt: `Copilot wants to run:\n\n${description}\n\nApprove this action?`,
|
|
170
|
+
choices: [PERMISSION_APPROVE_CHOICE, PERMISSION_DENY_CHOICE],
|
|
171
|
+
allowFreeform: true,
|
|
172
|
+
source: 'onPermissionRequest',
|
|
173
|
+
rationale: 'Copilot requested permission to run a tool in ask mode.',
|
|
174
|
+
extra: {
|
|
175
|
+
requestId: String(request?.requestId || '') || undefined,
|
|
176
|
+
permissionKind: String(request?.kind || '') || undefined,
|
|
177
|
+
},
|
|
178
|
+
}, { signal });
|
|
179
|
+
const answer = String(result?.answer ?? '').trim();
|
|
180
|
+
const approved = !result?.timedOut && answer.toLowerCase() === PERMISSION_APPROVE_CHOICE.toLowerCase();
|
|
181
|
+
// The denial note is composed HERE rather than in the permission handler so
|
|
182
|
+
// the handler never needs to know the card's choice labels — that would be
|
|
183
|
+
// an import cycle (this module already imports the request describer from
|
|
184
|
+
// the adapter). A freeform denial carries the human's own reason; a plain
|
|
185
|
+
// "Deny" click does not, so echoing the button label back at the model as
|
|
186
|
+
// though it were an explanation is avoided.
|
|
187
|
+
const feedback = answer && answer.toLowerCase() !== PERMISSION_DENY_CHOICE.toLowerCase()
|
|
188
|
+
? answer
|
|
189
|
+
: 'The user declined this action.';
|
|
190
|
+
return { approved, answer, feedback, timedOut: result?.timedOut === true, description };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Settle every card this worker is still waiting on. Called on shutdown: the
|
|
195
|
+
* process is about to stop polling, so a card left `pending` would sit in the
|
|
196
|
+
* UI inviting an answer that nothing will ever read.
|
|
197
|
+
*/
|
|
198
|
+
async function cancelPendingQuestions() {
|
|
199
|
+
const ids = [...pendingQuestionIds];
|
|
200
|
+
pendingQuestionIds.clear();
|
|
201
|
+
await Promise.all(ids.map((questionId) => api('POST', `/api/relay-question/${questionId}/timeout`, {})
|
|
202
|
+
.catch(() => {})));
|
|
203
|
+
return ids.length;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
askUserInput,
|
|
208
|
+
askToolApproval,
|
|
209
|
+
cancelPendingQuestions,
|
|
210
|
+
pendingQuestionCount: () => pendingQuestionIds.size,
|
|
211
|
+
// Re-exported so the runner can wait on a question id it created itself.
|
|
212
|
+
waitForRelayQuestionAnswer: shared.waitForRelayQuestionAnswer,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { QUESTION_TIMEOUT_CONTINUATION_TEXT };
|