@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,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import { execFileSync, spawn } from 'child_process';
|
|
4
|
+
import { createSessionWorkerProcessInspector } from './session-worker-process-service.mjs';
|
|
5
|
+
import { launchSessionCli } from './session-worker-launch-service.mjs';
|
|
6
|
+
|
|
7
|
+
function sleep(ms) {
|
|
8
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function isoNow(now) {
|
|
12
|
+
return new Date(now()).toISOString();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function parsePositiveInt(value) {
|
|
16
|
+
const num = Number.parseInt(String(value || ''), 10);
|
|
17
|
+
return Number.isInteger(num) && num > 0 ? num : null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createRelayCliLauncherService({
|
|
21
|
+
platform = process.platform,
|
|
22
|
+
cwd = process.cwd(),
|
|
23
|
+
env = process.env,
|
|
24
|
+
now = () => Date.now(),
|
|
25
|
+
execFileSyncImpl = execFileSync,
|
|
26
|
+
spawnImpl = spawn,
|
|
27
|
+
sleepImpl = sleep,
|
|
28
|
+
restartDelayMs = 500,
|
|
29
|
+
killWaitMs = 700,
|
|
30
|
+
maxKillAttempts = 10,
|
|
31
|
+
log = () => {},
|
|
32
|
+
} = {}) {
|
|
33
|
+
let activeJob = null;
|
|
34
|
+
const processInspector = createSessionWorkerProcessInspector({
|
|
35
|
+
platform,
|
|
36
|
+
execFileSyncImpl: typeof execFileSyncImpl === 'function' ? execFileSyncImpl : undefined,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
function resolveCwd(targetSessionId = null) {
|
|
40
|
+
if (typeof cwd === 'function') {
|
|
41
|
+
const next = String(cwd(targetSessionId) || '').trim();
|
|
42
|
+
return next || process.cwd();
|
|
43
|
+
}
|
|
44
|
+
const next = String(cwd || '').trim();
|
|
45
|
+
return next || process.cwd();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getState() {
|
|
49
|
+
if (!activeJob) return null;
|
|
50
|
+
return {
|
|
51
|
+
transactionId: activeJob.transactionId,
|
|
52
|
+
targetSessionId: activeJob.targetSessionId,
|
|
53
|
+
reason: activeJob.reason || null,
|
|
54
|
+
status: activeJob.status,
|
|
55
|
+
startedAt: activeJob.startedAt,
|
|
56
|
+
completedAt: activeJob.completedAt || null,
|
|
57
|
+
spawnedPid: activeJob.spawnedPid || null,
|
|
58
|
+
error: activeJob.error || null,
|
|
59
|
+
killedPids: Array.isArray(activeJob.killedPids) ? [...activeJob.killedPids] : [],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function collectWindowsRetirePids(targetSessionId) {
|
|
64
|
+
return processInspector.findWindowsProcessesForSession(targetSessionId)
|
|
65
|
+
.map((proc) => parsePositiveInt(proc?.processId))
|
|
66
|
+
.filter(Boolean);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function stopWindowsPids(pids) {
|
|
70
|
+
const ids = Array.from(new Set(
|
|
71
|
+
(Array.isArray(pids) ? pids : [pids])
|
|
72
|
+
.map((value) => parsePositiveInt(value))
|
|
73
|
+
.filter(Boolean),
|
|
74
|
+
));
|
|
75
|
+
if (!ids.length) return [];
|
|
76
|
+
const script = [
|
|
77
|
+
'$ids = @(' + ids.join(',') + ')',
|
|
78
|
+
'foreach ($id in $ids) {',
|
|
79
|
+
' try { Stop-Process -Id $id -Force -ErrorAction SilentlyContinue } catch {}',
|
|
80
|
+
'}',
|
|
81
|
+
].join('; ');
|
|
82
|
+
execFileSyncImpl('powershell.exe', ['-NoProfile', '-Command', script], {
|
|
83
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
84
|
+
});
|
|
85
|
+
return ids;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function retireCliProcesses(job) {
|
|
89
|
+
if (platform !== 'win32') return [];
|
|
90
|
+
const killed = new Set();
|
|
91
|
+
const attempts = Math.max(1, Number(maxKillAttempts || 1));
|
|
92
|
+
const liveTarget = processInspector.findWindowsProcessForSession(job.targetSessionId);
|
|
93
|
+
if (liveTarget?.processId) {
|
|
94
|
+
return Array.from(killed);
|
|
95
|
+
}
|
|
96
|
+
for (let index = 0; index < attempts; index += 1) {
|
|
97
|
+
let targetPids = [];
|
|
98
|
+
try {
|
|
99
|
+
targetPids = collectWindowsRetirePids(job.targetSessionId);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
job.error = error?.message || String(error);
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
if (!targetPids.length) return Array.from(killed);
|
|
105
|
+
for (const pid of stopWindowsPids(targetPids)) killed.add(pid);
|
|
106
|
+
await sleepImpl(Math.max(0, Number(killWaitMs || 0)));
|
|
107
|
+
}
|
|
108
|
+
return Array.from(killed);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function spawnSessionCli(targetSessionId) {
|
|
112
|
+
const launched = await launchSessionCli({
|
|
113
|
+
targetSessionId,
|
|
114
|
+
cwd: resolveCwd(targetSessionId),
|
|
115
|
+
env,
|
|
116
|
+
platform,
|
|
117
|
+
spawnImpl,
|
|
118
|
+
execFileSyncImpl,
|
|
119
|
+
processInspector,
|
|
120
|
+
});
|
|
121
|
+
return launched;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function runJob(job) {
|
|
125
|
+
job.status = 'running';
|
|
126
|
+
if (restartDelayMs > 0) {
|
|
127
|
+
await sleepImpl(restartDelayMs);
|
|
128
|
+
}
|
|
129
|
+
const liveTarget = processInspector.findProcessForSession(job.targetSessionId);
|
|
130
|
+
if (liveTarget?.processId) {
|
|
131
|
+
job.killedPids = [];
|
|
132
|
+
job.spawnedPid = liveTarget.processId;
|
|
133
|
+
job.status = 'skipped';
|
|
134
|
+
job.reason = 'target-already-running';
|
|
135
|
+
job.completedAt = isoNow(now);
|
|
136
|
+
log(`relay cli launcher: target ${job.targetSessionId} already running on pid ${liveTarget.processId}; skipping restart`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
log(`relay cli launcher: retiring cli processes for ${job.targetSessionId} tx=${job.transactionId || 'none'}`);
|
|
140
|
+
job.killedPids = await retireCliProcesses(job);
|
|
141
|
+
log(`relay cli launcher: spawning session-bound cli for ${job.targetSessionId}`);
|
|
142
|
+
const launched = await spawnSessionCli(job.targetSessionId);
|
|
143
|
+
job.spawnedPid = launched?.pid || null;
|
|
144
|
+
if (launched?.launchMode === 'tmux' && launched?.tmuxSessionName) {
|
|
145
|
+
log(`relay cli launcher: worker session ${job.targetSessionId} running in tmux session ${launched.tmuxSessionName}`);
|
|
146
|
+
}
|
|
147
|
+
job.status = 'spawned';
|
|
148
|
+
job.completedAt = isoNow(now);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function scheduleRestart({ targetSessionId, transactionId = null, reason = null } = {}) {
|
|
152
|
+
const target = String(targetSessionId || '').trim();
|
|
153
|
+
const tx = String(transactionId || '').trim() || null;
|
|
154
|
+
if (!target) {
|
|
155
|
+
return { ok: false, error: 'missing-target-session', state: getState() };
|
|
156
|
+
}
|
|
157
|
+
const current = activeJob;
|
|
158
|
+
if (current && !current.completedAt && current.status !== 'failed') {
|
|
159
|
+
if (current.targetSessionId === target && current.transactionId === tx) {
|
|
160
|
+
return { ok: true, accepted: true, reused: true, state: getState() };
|
|
161
|
+
}
|
|
162
|
+
return { ok: false, error: 'launcher-busy', state: getState() };
|
|
163
|
+
}
|
|
164
|
+
const liveTarget = processInspector.findProcessForSession(target);
|
|
165
|
+
if (liveTarget?.processId) {
|
|
166
|
+
return {
|
|
167
|
+
ok: true,
|
|
168
|
+
accepted: false,
|
|
169
|
+
reused: true,
|
|
170
|
+
reason: 'target-already-running',
|
|
171
|
+
livePid: liveTarget.processId,
|
|
172
|
+
state: getState(),
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const job = {
|
|
177
|
+
transactionId: tx,
|
|
178
|
+
targetSessionId: target,
|
|
179
|
+
reason: String(reason || '').trim() || null,
|
|
180
|
+
startedAt: isoNow(now),
|
|
181
|
+
completedAt: null,
|
|
182
|
+
status: 'scheduled',
|
|
183
|
+
spawnedPid: null,
|
|
184
|
+
error: null,
|
|
185
|
+
killedPids: [],
|
|
186
|
+
promise: null,
|
|
187
|
+
};
|
|
188
|
+
activeJob = job;
|
|
189
|
+
job.promise = runJob(job).catch((error) => {
|
|
190
|
+
job.status = 'failed';
|
|
191
|
+
job.completedAt = isoNow(now);
|
|
192
|
+
job.error = error?.message || String(error);
|
|
193
|
+
log(`relay cli launcher failed for ${job.targetSessionId}: ${job.error}`);
|
|
194
|
+
});
|
|
195
|
+
return { ok: true, accepted: true, reused: false, state: getState() };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function waitForIdle() {
|
|
199
|
+
if (activeJob?.promise) {
|
|
200
|
+
await activeJob.promise;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
scheduleRestart,
|
|
206
|
+
waitForIdle,
|
|
207
|
+
getState,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const DEFAULT_RELAY_TOOL_GUIDANCE = [
|
|
8
|
+
'# Relay Tool Guidance',
|
|
9
|
+
'For any user-facing question or clarification, use the ask_user tool so the web relay can render question cards and buttons. Never ask questions in plain assistant text.',
|
|
10
|
+
'When using ask_user, ALWAYS include a `choices` array with 2-6 answer options so the web relay can render clickable buttons. Only omit choices when the question genuinely requires freeform text input.',
|
|
11
|
+
'In autopilot, still call ask_user when user input is truly blocking, because the relay bridge can surface the question even when the direct SDK question hook is bypassed.',
|
|
12
|
+
'For relay restarts in extension-managed mode, require explicit user permission first, then use the authenticated localhost API `POST /api/relay/shutdown`. Do not restart by killing processes or using respawn scripts.',
|
|
13
|
+
'Note: shutdown is queued and only completes when the current turn finishes, so it is pointless to wait for it to interrupt an active turn.',
|
|
14
|
+
'Use `restart: true` in the request body when the user wants a real relay restart rather than a plain shutdown. Example request body: `{ "reason": "manual-restart", "requestedBy": "localhost-api", "restart": true }`.',
|
|
15
|
+
].join(' ');
|
|
16
|
+
|
|
17
|
+
function loadRelayToolGuidance() {
|
|
18
|
+
try {
|
|
19
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const guidancePath = path.resolve(moduleDir, '..', 'relay-tools.md');
|
|
21
|
+
const content = fs.readFileSync(guidancePath, 'utf8');
|
|
22
|
+
const parts = String(content || '')
|
|
23
|
+
.split(/\r?\n/)
|
|
24
|
+
.map((line) => String(line || '').trim())
|
|
25
|
+
.filter(Boolean);
|
|
26
|
+
return parts.length ? parts.join(' ') : DEFAULT_RELAY_TOOL_GUIDANCE;
|
|
27
|
+
} catch {
|
|
28
|
+
return DEFAULT_RELAY_TOOL_GUIDANCE;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const RELAY_TOOL_GUIDANCE = loadRelayToolGuidance();
|
|
33
|
+
|
|
34
|
+
const MODE_MARKERS = {
|
|
35
|
+
plan: '[Relay mode: plan]',
|
|
36
|
+
ask: '[Relay mode: ask]',
|
|
37
|
+
autopilot: '[Relay mode: autopilot]',
|
|
38
|
+
agent: '[Relay mode: agent]',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const MODE_INSTRUCTIONS = {
|
|
42
|
+
plan: [
|
|
43
|
+
'Draft a concise plan only.',
|
|
44
|
+
'Use read-only inspection tools (glob, rg, view) only when they materially improve plan quality; otherwise draft from provided context.',
|
|
45
|
+
'Do not edit repository files or run mutating commands unless the user explicitly asks for implementation.',
|
|
46
|
+
'If clarification is required, pause and ask through the web relay.',
|
|
47
|
+
'These instructions remain in effect until relay mode changes.',
|
|
48
|
+
].join(' '),
|
|
49
|
+
ask: [
|
|
50
|
+
'Prioritize clarification questions before doing any implementation work.',
|
|
51
|
+
'If the request is ambiguous or underspecified, pause and ask through the web relay before making assumptions.',
|
|
52
|
+
'Do not make broad assumptions when a question would materially change the result.',
|
|
53
|
+
'These instructions remain in effect until relay mode changes.',
|
|
54
|
+
].join(' '),
|
|
55
|
+
autopilot: [
|
|
56
|
+
'Act directly on the request and use tools when needed.',
|
|
57
|
+
'Keep moving unless user input is truly blocking.',
|
|
58
|
+
'These instructions remain in effect until relay mode changes.',
|
|
59
|
+
].join(' '),
|
|
60
|
+
agent: [
|
|
61
|
+
'Proceed as an interactive coding agent and use tools as needed.',
|
|
62
|
+
'If you need clarification, pause and ask through the web relay instead of stalling silently.',
|
|
63
|
+
'These instructions remain in effect until relay mode changes.',
|
|
64
|
+
].join(' '),
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const LEGACY_MODE_INSTRUCTIONS = {
|
|
68
|
+
plan: [
|
|
69
|
+
'Draft a concise plan only.',
|
|
70
|
+
'Do not edit files or run tools unless the user explicitly asks for implementation.',
|
|
71
|
+
'If clarification is required, pause and ask through the web relay.',
|
|
72
|
+
].join(' '),
|
|
73
|
+
ask: [
|
|
74
|
+
'Prioritize clarification questions before doing any implementation work.',
|
|
75
|
+
'If the request is ambiguous or underspecified, pause and ask through the web relay before making assumptions.',
|
|
76
|
+
'Do not make broad assumptions when a question would materially change the result.',
|
|
77
|
+
].join(' '),
|
|
78
|
+
autopilot: [
|
|
79
|
+
'Act directly on the request and use tools when needed.',
|
|
80
|
+
'Keep moving unless user input is truly blocking.',
|
|
81
|
+
].join(' '),
|
|
82
|
+
agent: [
|
|
83
|
+
'Proceed as an interactive coding agent and use tools as needed.',
|
|
84
|
+
'If you need clarification, pause and ask through the web relay instead of stalling silently.',
|
|
85
|
+
].join(' '),
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function escapeRegExp(value) {
|
|
89
|
+
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function whitespaceFlexiblePattern(value) {
|
|
93
|
+
return String(value || '')
|
|
94
|
+
.trim()
|
|
95
|
+
.split(/\s+/)
|
|
96
|
+
.filter(Boolean)
|
|
97
|
+
.map((part) => escapeRegExp(part))
|
|
98
|
+
.join('\\s+');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildPromptPrefixPatterns(mode) {
|
|
102
|
+
const requestedMode = String(mode || '').trim().toLowerCase();
|
|
103
|
+
const modes = requestedMode && MODE_MARKERS[requestedMode]
|
|
104
|
+
? [requestedMode]
|
|
105
|
+
: Object.keys(MODE_MARKERS);
|
|
106
|
+
return modes.flatMap((key) => {
|
|
107
|
+
const marker = MODE_MARKERS[key];
|
|
108
|
+
const currentFullPrefix = `${marker} ${MODE_INSTRUCTIONS[key]} ${RELAY_TOOL_GUIDANCE}`;
|
|
109
|
+
const legacyFullPrefix = `${marker} ${LEGACY_MODE_INSTRUCTIONS[key]} ${RELAY_TOOL_GUIDANCE}`;
|
|
110
|
+
return [
|
|
111
|
+
new RegExp(`^${whitespaceFlexiblePattern(currentFullPrefix)}\\s*`, 'i'),
|
|
112
|
+
new RegExp(`^${whitespaceFlexiblePattern(legacyFullPrefix)}\\s*`, 'i'),
|
|
113
|
+
new RegExp(`^${whitespaceFlexiblePattern(marker)}\\s*`, 'i'),
|
|
114
|
+
];
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function stripAttachmentPromptArtifacts(text) {
|
|
119
|
+
return String(text || '')
|
|
120
|
+
.replace(/<current_datetime>[\s\S]*?<\/current_datetime>/gi, '')
|
|
121
|
+
.replace(/<system_reminder>[\s\S]*?<\/system_reminder>/gi, '')
|
|
122
|
+
.replace(/\[Attached file references\][\s\S]*?\[\/Attached file references\]/gi, '')
|
|
123
|
+
.replace(/\[Attached image(?:s)?\s*:[\s\S]*?\]/gi, '')
|
|
124
|
+
.replace(/\[Attached file\s*:[\s\S]*?\]/gi, '')
|
|
125
|
+
.trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function stripRelayPromptContext(text, relayMode = '', attachments = []) {
|
|
129
|
+
const value = stripAttachmentPromptArtifacts(text);
|
|
130
|
+
if (!value) return '';
|
|
131
|
+
const patterns = buildPromptPrefixPatterns(relayMode);
|
|
132
|
+
for (const pattern of patterns) {
|
|
133
|
+
const stripped = value.replace(pattern, '').trim();
|
|
134
|
+
if (stripped && stripped !== value) return stripped;
|
|
135
|
+
}
|
|
136
|
+
return value;
|
|
137
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
export function shouldParkForRestart(state) {
|
|
4
|
+
const value = String(state?.state || '').trim();
|
|
5
|
+
return value === 'draining' || value === 'restarting' || value === 'awaiting_rebind';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function parkPendingQueueForRestart({ stmts, state, parkedAt = null } = {}) {
|
|
9
|
+
if (!stmts || !shouldParkForRestart(state)) return 0;
|
|
10
|
+
const result = stmts.parkPendingQueueForRestart.run({
|
|
11
|
+
parkedAt: parkedAt || new Date().toISOString(),
|
|
12
|
+
targetSessionId: state?.targetSessionId || null,
|
|
13
|
+
transactionId: state?.transactionId || null,
|
|
14
|
+
reason: state?.lastError || null,
|
|
15
|
+
});
|
|
16
|
+
return Number(result?.changes || 0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function releaseParkedQueueForReadyState({ db, stmts, state } = {}) {
|
|
20
|
+
const value = String(state?.state || '').trim();
|
|
21
|
+
const terminalIdle = value === 'idle' && String(state?.terminalOutcomeCode || '').trim();
|
|
22
|
+
if (!db || !stmts || (value !== 'ready' && !terminalIdle)) return [];
|
|
23
|
+
const rows = terminalIdle
|
|
24
|
+
? stmts.listAllParkedQueueForRelease.all()
|
|
25
|
+
: stmts.listParkedQueueForRelease.all(
|
|
26
|
+
state?.transactionId || null,
|
|
27
|
+
state?.targetSessionId || null,
|
|
28
|
+
);
|
|
29
|
+
if (!rows.length) return [];
|
|
30
|
+
const tx = db.transaction(() => {
|
|
31
|
+
for (const row of rows) {
|
|
32
|
+
stmts.releaseParkedQueueByIds.run(row.id);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
tx();
|
|
36
|
+
return rows;
|
|
37
|
+
}
|
|
38
|
+
|