openpond 0.0.30-bootstrap.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/CHANGELOG.md +128 -0
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/RELEASE.md +20 -0
- package/dist/api/core.d.ts +22 -0
- package/dist/api/deployments.d.ts +30 -0
- package/dist/api/types.d.ts +624 -0
- package/dist/api.d.ts +90 -0
- package/dist/cache.d.ts +22 -0
- package/dist/chunks/app-layer-YKXEQGOW.js +163 -0
- package/dist/chunks/apps-GGSIN2ZF.js +539 -0
- package/dist/chunks/chunk-3CC6MH35.js +1663 -0
- package/dist/chunks/chunk-3Y4EL3P6.js +1090 -0
- package/dist/chunks/chunk-4HYJG2JE.js +3875 -0
- package/dist/chunks/chunk-5G4E547X.js +294 -0
- package/dist/chunks/chunk-74O6FGVN.js +598 -0
- package/dist/chunks/chunk-AUJLPKGI.js +632 -0
- package/dist/chunks/chunk-BYAPLT37.js +744 -0
- package/dist/chunks/chunk-FUP7EGT7.js +1845 -0
- package/dist/chunks/chunk-I6HN3ZGW.js +88 -0
- package/dist/chunks/chunk-KRKGHVMQ.js +294 -0
- package/dist/chunks/chunk-LHYK6QO2.js +1300 -0
- package/dist/chunks/chunk-NLBKZZEK.js +224 -0
- package/dist/chunks/chunk-OG75GMVO.js +1 -0
- package/dist/chunks/chunk-Q57SVPEH.js +11663 -0
- package/dist/chunks/chunk-UACZUFIA.js +47 -0
- package/dist/chunks/chunk-VQX33YAG.js +5135 -0
- package/dist/chunks/chunk-W5GKHH77.js +168 -0
- package/dist/chunks/chunk-WYDVQOBL.js +17581 -0
- package/dist/chunks/chunk-XSAXENQD.js +4167 -0
- package/dist/chunks/chunk-Y6G6WRTG.js +210 -0
- package/dist/chunks/cli-NVKNE4AZ.js +11 -0
- package/dist/chunks/cli-X3HPLSOV.js +19 -0
- package/dist/chunks/core-commands-IM2UKLU4.js +41 -0
- package/dist/chunks/extend-YXCBULPO.js +84 -0
- package/dist/chunks/harness-GMK6IY2U.js +140 -0
- package/dist/chunks/help-LMH5FGT2.js +65 -0
- package/dist/chunks/opchat-BWXZ32GX.js +169 -0
- package/dist/chunks/organizations-4ISQRGMQ.js +738 -0
- package/dist/chunks/profile-NLDZDT3L.js +1110 -0
- package/dist/chunks/project-agent-ZPVO25HA.js +892 -0
- package/dist/chunks/sandbox-command-5GFJ2U3V.js +1652 -0
- package/dist/chunks/sandbox-template-LZN4F55Z.js +1646 -0
- package/dist/chunks/src-3RBI4DNN.js +3595 -0
- package/dist/chunks/src-EYXQS6XD.js +73427 -0
- package/dist/chunks/teams-bot-WP3FHKGA.js +137 -0
- package/dist/cli.js +77 -0
- package/dist/client-types.d.ts +222 -0
- package/dist/client.d.ts +19 -0
- package/dist/config.d.ts +184 -0
- package/dist/hosted-chat.d.ts +162 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +25481 -0
- package/dist/indicators.d.ts +38 -0
- package/dist/opchat-smoke.d.ts +47 -0
- package/dist/private-json-file.d.ts +3 -0
- package/dist/profile/local-profile-types.d.ts +160 -0
- package/dist/profile/local-profile.d.ts +69 -0
- package/dist/profile/profile-catalog.d.ts +14 -0
- package/dist/profile/profile-git.d.ts +17 -0
- package/dist/profile/profile-promotion.d.ts +21 -0
- package/dist/profile/profile-setup-gate.d.ts +22 -0
- package/dist/profile/profile-skills.d.ts +30 -0
- package/dist/profile/profile-source-upload.d.ts +29 -0
- package/dist/profile/source-upload-cache.d.ts +29 -0
- package/dist/sandbox/async-request-options.d.ts +2 -0
- package/dist/sandbox/client-handles.d.ts +113 -0
- package/dist/sandbox/client.d.ts +348 -0
- package/dist/sandbox/runtime-handle.d.ts +36 -0
- package/dist/sandbox/sandbox-instance-client.d.ts +72 -0
- package/dist/sandbox/smoke.d.ts +3 -0
- package/dist/sandbox/stream.d.ts +7 -0
- package/dist/sandbox/types/billing.d.ts +176 -0
- package/dist/sandbox/types/client.d.ts +103 -0
- package/dist/sandbox/types/core.d.ts +140 -0
- package/dist/sandbox/types/index.d.ts +14 -0
- package/dist/sandbox/types/io.d.ts +240 -0
- package/dist/sandbox/types/microsoft-teams-bot.d.ts +51 -0
- package/dist/sandbox/types/org-project-agent.d.ts +610 -0
- package/dist/sandbox/types/record.d.ts +53 -0
- package/dist/sandbox/types/responses.d.ts +295 -0
- package/dist/sandbox/types/runtime-profiles.d.ts +15 -0
- package/dist/sandbox/types/runtime.d.ts +152 -0
- package/dist/sandbox/types/schedules.d.ts +141 -0
- package/dist/sandbox/types/snapshots.d.ts +205 -0
- package/dist/sandbox/types/workflow-modes.d.ts +1 -0
- package/dist/sandbox/types/workload-sources.d.ts +20 -0
- package/dist/sandbox/url.d.ts +2 -0
- package/dist/sandbox-template/manifest.d.ts +304 -0
- package/dist/sandbox-template/manifest.js +18381 -0
- package/dist/skills/openpond-cli/SKILL.md +49 -0
- package/dist/skills/openpond-desktop-harness/SKILL.md +113 -0
- package/dist/skills/openpond-desktop-harness/agents/openai.yaml +4 -0
- package/dist/skills/openpond-taskset-authoring/SKILL.md +38 -0
- package/dist/skills/openpond-taskset-authoring/references/graders-and-rewards.md +11 -0
- package/dist/skills/openpond-taskset-authoring/references/method-selection.md +12 -0
- package/dist/skills/openpond-taskset-authoring/references/privacy-and-provenance.md +8 -0
- package/dist/skills/openpond-taskset-authoring/references/task-design.md +13 -0
- package/dist/stream.d.ts +20 -0
- package/dist/types.d.ts +57 -0
- package/dist/urls.d.ts +4 -0
- package/dist/web/assets/AppsView-Bu-Nc3Ew.js +1 -0
- package/dist/web/assets/AppsView-DIhprTIC.css +1 -0
- package/dist/web/assets/BrowserSidebar-CCguQ-DR.js +1 -0
- package/dist/web/assets/BrowserSidebar-D9i7gQzw.css +1 -0
- package/dist/web/assets/CloudWorkView-DNoPIlMj.css +1 -0
- package/dist/web/assets/CloudWorkView-Dr3xqLE5.js +1 -0
- package/dist/web/assets/CommandMenu-CnodJFHD.js +1 -0
- package/dist/web/assets/CommandMenu-hNQCm8mw.css +1 -0
- package/dist/web/assets/CommunityView-CIbjNUZr.js +1 -0
- package/dist/web/assets/CommunityView-DSik4sd2.css +1 -0
- package/dist/web/assets/GetStartedView-C3ZrnEz5.css +1 -0
- package/dist/web/assets/GetStartedView-C8nyH_PT.js +1 -0
- package/dist/web/assets/LabsExtensions-GtJzxDnK.js +1 -0
- package/dist/web/assets/LabsProfile-DG7rWYur.js +1 -0
- package/dist/web/assets/LabsRoute-D6MDHBzq.js +2 -0
- package/dist/web/assets/LabsRoute-TEt9j1vB.css +1 -0
- package/dist/web/assets/LabsSignals-BfsDN86w.js +1 -0
- package/dist/web/assets/LabsSignals-D6iA6Ryl.css +1 -0
- package/dist/web/assets/NewProjectDialog-CaKiCoVS.js +1 -0
- package/dist/web/assets/ProfileSettingsSection-DTQS2DVL.css +1 -0
- package/dist/web/assets/ProfileSettingsSection-DxEzoF3K.js +1 -0
- package/dist/web/assets/ProfileView-CEscW7Ru.js +1 -0
- package/dist/web/assets/SettingsView-DwbGu8Kj.css +1 -0
- package/dist/web/assets/SettingsView-JIRR_FO4.js +63 -0
- package/dist/web/assets/TeamChatView-B5M1ThhJ.js +3 -0
- package/dist/web/assets/TeamChatView-Bx44q3WV.css +1 -0
- package/dist/web/assets/TerminalOverlay-C1iP4OS1.js +19 -0
- package/dist/web/assets/TerminalOverlay-DwBiZLwk.css +1 -0
- package/dist/web/assets/TrainingSettingsSection-BDYvI2te.js +1 -0
- package/dist/web/assets/TrainingView-CoCFK6W3.js +5 -0
- package/dist/web/assets/WorkspaceDiffPanel-CMxKQeo7.js +14 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-B8JJbwIz.js +32 -0
- package/dist/web/assets/WorkspaceEnvironmentMenu-DGIFdG3o.css +1 -0
- package/dist/web/assets/WorkspaceGitDialogs-B4sp9BGn.js +1 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BBqimG5X.js +845 -0
- package/dist/web/assets/WorkspaceMonacoEditor-BwKcXIZw.css +1 -0
- package/dist/web/assets/boxes-DAaDUabq.js +1 -0
- package/dist/web/assets/codicon-ngg6Pgfi.ttf +0 -0
- package/dist/web/assets/css.worker-DBVD8oXr.js +93 -0
- package/dist/web/assets/cssMode-CBF1mieJ.js +1 -0
- package/dist/web/assets/dockerfile-BG73LgW2.js +1 -0
- package/dist/web/assets/editor.worker-CdQrwHl8.js +26 -0
- package/dist/web/assets/git-commit-horizontal-CAickXpN.js +1 -0
- package/dist/web/assets/git-dialogs-BZDqS1eT.css +1 -0
- package/dist/web/assets/html.worker-CwpTb9lJ.js +470 -0
- package/dist/web/assets/htmlMode-2u8OHtWO.js +1 -0
- package/dist/web/assets/index-BdY6A-YN.css +1 -0
- package/dist/web/assets/index-CU2qhUYV.js +195 -0
- package/dist/web/assets/index-Dz2B3-4C.js +1 -0
- package/dist/web/assets/info-CQNTh78o.js +1 -0
- package/dist/web/assets/json.worker-BoL8UZqY.js +58 -0
- package/dist/web/assets/jsonMode-C4CWXViX.js +7 -0
- package/dist/web/assets/lspLanguageFeatures-CBs_BOlB.js +4 -0
- package/dist/web/assets/markdown-BFxVWTOG.js +1 -0
- package/dist/web/assets/monaco.contribution-C1LUxAKf.js +2 -0
- package/dist/web/assets/monaco.contribution-Cpf6dWsh.js +2 -0
- package/dist/web/assets/monaco.contribution-lOXpG2xQ.js +2 -0
- package/dist/web/assets/monaco.contribution-sd3cFkcN.js +2 -0
- package/dist/web/assets/monitor-DsIswrMV.js +1 -0
- package/dist/web/assets/play-CeRw21NV.js +1 -0
- package/dist/web/assets/python-CXYq3BUl.js +1 -0
- package/dist/web/assets/refresh-cw-N5b_6AP5.js +1 -0
- package/dist/web/assets/reply-BSKjZrJg.js +1 -0
- package/dist/web/assets/save-D0oHH1a3.js +1 -0
- package/dist/web/assets/settings-lists-BEg6LMcy.css +1 -0
- package/dist/web/assets/shell-DiJ1NA_G.js +1 -0
- package/dist/web/assets/toggleHighContrast-B7NOMXDY.css +1 -0
- package/dist/web/assets/toggleHighContrast-DRjlcwes.js +62 -0
- package/dist/web/assets/training-model-data-BPrc68VG.js +1 -0
- package/dist/web/assets/ts.worker-BH9nVgjN.js +67718 -0
- package/dist/web/assets/tsMode-x-1A7ZcB.js +11 -0
- package/dist/web/assets/wifi-off-Dt52ofzh.js +1 -0
- package/dist/web/assets/workers-Ciff6Dzi.js +1 -0
- package/dist/web/assets/yaml-CB67Kxnz.js +1 -0
- package/dist/web/connected-apps/github.svg +3 -0
- package/dist/web/connected-apps/google.svg +6 -0
- package/dist/web/connected-apps/linear.svg +3 -0
- package/dist/web/connected-apps/microsoft.svg +6 -0
- package/dist/web/connected-apps/notion.svg +3 -0
- package/dist/web/connected-apps/openpond-mcp.svg +4 -0
- package/dist/web/connected-apps/slack.svg +6 -0
- package/dist/web/connected-apps/x.svg +3 -0
- package/dist/web/favicon-16x16.png +0 -0
- package/dist/web/favicon-32x32.png +0 -0
- package/dist/web/index.html +16 -0
- package/dist/web/openpond-icon.png +0 -0
- package/dist/web/openpond-wordlogo-white.png +0 -0
- package/dist/web/tokens/eth.svg +1 -0
- package/dist/web/tokens/usdc.svg +1 -0
- package/docs/agent-edit-check-status-json.md +132 -0
- package/docs/command-reference.md +695 -0
- package/package.json +84 -0
|
@@ -0,0 +1,3595 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __openpondCreateRequire } from "node:module"; var require = __openpondCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
ApprovalSchema,
|
|
5
|
+
ContextUsageSnapshotSchema,
|
|
6
|
+
DEFAULT_CHAT_PROVIDER,
|
|
7
|
+
DEFAULT_CODEX_CHAT_MODEL,
|
|
8
|
+
DEFAULT_OPENPOND_CHAT_MODEL,
|
|
9
|
+
DEFAULT_OPENPOND_COMMAND_ACCESS_MODE,
|
|
10
|
+
OpenPondCommandAccessModeSchema,
|
|
11
|
+
createReadyLineParser
|
|
12
|
+
} from "./chunk-Q57SVPEH.js";
|
|
13
|
+
import {
|
|
14
|
+
CONNECTED_APP_CATALOG,
|
|
15
|
+
buildConnectedAppInstallUrl,
|
|
16
|
+
connectedAppById
|
|
17
|
+
} from "./chunk-LHYK6QO2.js";
|
|
18
|
+
import "./chunk-WYDVQOBL.js";
|
|
19
|
+
import "./chunk-UACZUFIA.js";
|
|
20
|
+
|
|
21
|
+
// ../terminal/src/index.ts
|
|
22
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
23
|
+
import { createInterface } from "node:readline/promises";
|
|
24
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
25
|
+
import { pathToFileURL } from "node:url";
|
|
26
|
+
|
|
27
|
+
// ../terminal/src/ui/composer.ts
|
|
28
|
+
function createComposer() {
|
|
29
|
+
return { text: "", cursor: 0, history: [], historyIndex: null };
|
|
30
|
+
}
|
|
31
|
+
function insertText(state, value) {
|
|
32
|
+
const before = state.text.slice(0, state.cursor);
|
|
33
|
+
const after = state.text.slice(state.cursor);
|
|
34
|
+
return {
|
|
35
|
+
...state,
|
|
36
|
+
text: `${before}${value}${after}`,
|
|
37
|
+
cursor: state.cursor + value.length,
|
|
38
|
+
historyIndex: null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function replaceComposerText(state, text) {
|
|
42
|
+
return { ...state, text, cursor: text.length };
|
|
43
|
+
}
|
|
44
|
+
function submitComposer(state) {
|
|
45
|
+
const text = state.text.trim();
|
|
46
|
+
if (!text) return { state: { ...state, text: "", cursor: 0, historyIndex: null }, action: { type: "none" } };
|
|
47
|
+
return {
|
|
48
|
+
state: {
|
|
49
|
+
text: "",
|
|
50
|
+
cursor: 0,
|
|
51
|
+
history: [...state.history.filter((item) => item !== text), text].slice(-100),
|
|
52
|
+
historyIndex: null
|
|
53
|
+
},
|
|
54
|
+
action: { type: "submit", text }
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function handleComposerKey(state, key) {
|
|
58
|
+
if (key === "ctrl+c") return { state, action: { type: "cancel-or-exit" } };
|
|
59
|
+
if (key === "ctrl+d") return { state, action: state.text ? { type: "none" } : { type: "exit" } };
|
|
60
|
+
if (key === "ctrl+l") return { state, action: { type: "rerender" } };
|
|
61
|
+
if (key === "enter") return submitComposer(state);
|
|
62
|
+
if (key === "alt+enter") return { state: insertText(state, "\n"), action: { type: "none" } };
|
|
63
|
+
if (key === "backspace") {
|
|
64
|
+
if (state.cursor <= 0) return { state, action: { type: "none" } };
|
|
65
|
+
return {
|
|
66
|
+
state: {
|
|
67
|
+
...state,
|
|
68
|
+
text: `${state.text.slice(0, state.cursor - 1)}${state.text.slice(state.cursor)}`,
|
|
69
|
+
cursor: state.cursor - 1
|
|
70
|
+
},
|
|
71
|
+
action: { type: "none" }
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (key === "delete") {
|
|
75
|
+
if (state.cursor >= state.text.length) return { state, action: { type: "none" } };
|
|
76
|
+
return {
|
|
77
|
+
state: {
|
|
78
|
+
...state,
|
|
79
|
+
text: `${state.text.slice(0, state.cursor)}${state.text.slice(state.cursor + 1)}`
|
|
80
|
+
},
|
|
81
|
+
action: { type: "none" }
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (key === "left") return { state: { ...state, cursor: Math.max(0, state.cursor - 1) }, action: { type: "none" } };
|
|
85
|
+
if (key === "right") {
|
|
86
|
+
return { state: { ...state, cursor: Math.min(state.text.length, state.cursor + 1) }, action: { type: "none" } };
|
|
87
|
+
}
|
|
88
|
+
if (key === "home" || key === "ctrl+a") return { state: { ...state, cursor: 0 }, action: { type: "none" } };
|
|
89
|
+
if (key === "end" || key === "ctrl+e") {
|
|
90
|
+
return { state: { ...state, cursor: state.text.length }, action: { type: "none" } };
|
|
91
|
+
}
|
|
92
|
+
if (key === "ctrl+u") return { state: { ...state, text: state.text.slice(state.cursor), cursor: 0 }, action: { type: "none" } };
|
|
93
|
+
if (key === "ctrl+k") return { state: { ...state, text: state.text.slice(0, state.cursor) }, action: { type: "none" } };
|
|
94
|
+
if (key === "up") {
|
|
95
|
+
if (state.history.length === 0) return { state, action: { type: "none" } };
|
|
96
|
+
const nextIndex = state.historyIndex === null ? state.history.length - 1 : Math.max(0, state.historyIndex - 1);
|
|
97
|
+
return { state: replaceComposerText({ ...state, historyIndex: nextIndex }, state.history[nextIndex] ?? ""), action: { type: "none" } };
|
|
98
|
+
}
|
|
99
|
+
if (key === "down") {
|
|
100
|
+
if (state.historyIndex === null) return { state, action: { type: "none" } };
|
|
101
|
+
const nextIndex = state.historyIndex + 1;
|
|
102
|
+
if (nextIndex >= state.history.length) {
|
|
103
|
+
return { state: { ...state, text: "", cursor: 0, historyIndex: null }, action: { type: "none" } };
|
|
104
|
+
}
|
|
105
|
+
return { state: replaceComposerText({ ...state, historyIndex: nextIndex }, state.history[nextIndex] ?? ""), action: { type: "none" } };
|
|
106
|
+
}
|
|
107
|
+
if (key.length > 0) return { state: insertText(state, key), action: { type: "none" } };
|
|
108
|
+
return { state, action: { type: "none" } };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ../terminal/src/line-mode-turn-guard.ts
|
|
112
|
+
var LINE_MODE_TURN_RUNNING_MESSAGE = "A turn is already running. Wait for it to finish before sending another line.";
|
|
113
|
+
function createTerminalTurnSubmissionGuard() {
|
|
114
|
+
let running = false;
|
|
115
|
+
return {
|
|
116
|
+
isRunning() {
|
|
117
|
+
return running;
|
|
118
|
+
},
|
|
119
|
+
tryStartSubmission() {
|
|
120
|
+
if (running) return false;
|
|
121
|
+
running = true;
|
|
122
|
+
return true;
|
|
123
|
+
},
|
|
124
|
+
failSubmission() {
|
|
125
|
+
running = false;
|
|
126
|
+
},
|
|
127
|
+
applyRuntimeEvent(event) {
|
|
128
|
+
if (event.name === "turn.started") running = true;
|
|
129
|
+
if (event.name === "turn.completed" || event.name === "turn.failed" || event.name === "turn.interrupted") {
|
|
130
|
+
running = false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function createLineModeTurnGuard() {
|
|
136
|
+
return createTerminalTurnSubmissionGuard();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ../terminal/src/ui/commands.ts
|
|
140
|
+
var SLASH_COMMANDS = [
|
|
141
|
+
{ name: "help", usage: "/help", description: "show this help" },
|
|
142
|
+
{ name: "providers", usage: "/providers", description: "list providers" },
|
|
143
|
+
{ name: "provider", usage: "/provider [id]", description: "switch provider", requiresArgument: true },
|
|
144
|
+
{ name: "model", usage: "/model [id]", description: "list or switch model" },
|
|
145
|
+
{ name: "apps", usage: "/apps", description: "list connectable apps" },
|
|
146
|
+
{ name: "install", usage: "/install <app>", description: "open app details", requiresArgument: true },
|
|
147
|
+
{ name: "projects", usage: "/projects", description: "list projects" },
|
|
148
|
+
{ name: "project", usage: "/project <id>", description: "switch project", requiresArgument: true },
|
|
149
|
+
{ name: "permissions", usage: "/permissions [ask|full-access]", description: "show or change command access" },
|
|
150
|
+
{ name: "profile", usage: "/profile [status|diff|catalog|check|push]", description: "show or sync profile" },
|
|
151
|
+
{ name: "agents", usage: "/agents", description: "list agents" },
|
|
152
|
+
{ name: "agent", usage: "/agent <id>", description: "select agent", requiresArgument: true },
|
|
153
|
+
{ name: "run", usage: "/run <action> [json]", description: "run profile action", requiresArgument: true },
|
|
154
|
+
{ name: "settings", usage: "/settings [goal-storage global|workspace]", description: "show or update settings" },
|
|
155
|
+
{ name: "logs", usage: "/logs", description: "show recent events" },
|
|
156
|
+
{ name: "compact", usage: "/compact", description: "compact current session" },
|
|
157
|
+
{ name: "clear", usage: "/clear", description: "clear terminal transcript" },
|
|
158
|
+
{ name: "exit", usage: "/exit", description: "exit" },
|
|
159
|
+
{ name: "hooks", usage: "/hooks", description: "show hook status" },
|
|
160
|
+
{ name: "start", usage: "/start", description: "start a new session" }
|
|
161
|
+
];
|
|
162
|
+
function parseSlashCommand(text) {
|
|
163
|
+
if (!text.startsWith("/")) return null;
|
|
164
|
+
const [rawCommand = "", ...rest] = text.slice(1).trim().split(/\s+/);
|
|
165
|
+
const command = rawCommand.toLowerCase();
|
|
166
|
+
if (!command) return null;
|
|
167
|
+
if (command === "exit" || command === "quit") return { type: "exit" };
|
|
168
|
+
if (command === "help") return { type: "help" };
|
|
169
|
+
if (command === "clear") return { type: "clear" };
|
|
170
|
+
if (command === "agents") return { type: "agents" };
|
|
171
|
+
if (command === "apps") return { type: "apps" };
|
|
172
|
+
if (command === "agent") return { type: "agent", id: rest.join(" ").trim() };
|
|
173
|
+
if (command === "install") return { type: "install", id: rest.join(" ").trim() };
|
|
174
|
+
if (command === "projects") return { type: "projects" };
|
|
175
|
+
if (command === "project") return { type: "project", id: rest.join(" ").trim() };
|
|
176
|
+
if (command === "model") return { type: "model", id: rest.join(" ").trim() || null };
|
|
177
|
+
if (command === "permissions") return { type: "permissions", args: rest };
|
|
178
|
+
if (command === "profile") return { type: "profile", args: rest };
|
|
179
|
+
if (command === "logs") return { type: "logs" };
|
|
180
|
+
if (command === "providers") return { type: "providers" };
|
|
181
|
+
if (command === "provider") return { type: "provider", id: rest.join(" ").trim() || null };
|
|
182
|
+
if (command === "compact") return { type: "compact" };
|
|
183
|
+
if (command === "hooks") return { type: "hooks" };
|
|
184
|
+
if (command === "start") return { type: "start" };
|
|
185
|
+
if (command === "run") {
|
|
186
|
+
const action = rest[0] ?? "";
|
|
187
|
+
const rawInput = rest.slice(1).join(" ").trim();
|
|
188
|
+
if (!rawInput) return { type: "run", action, input: void 0 };
|
|
189
|
+
return { type: "run", action, input: JSON.parse(rawInput) };
|
|
190
|
+
}
|
|
191
|
+
if (command === "settings") return { type: "settings", args: rest };
|
|
192
|
+
return { type: "unknown", command };
|
|
193
|
+
}
|
|
194
|
+
function parseDirectCommandPrompt(text) {
|
|
195
|
+
const trimmed = text.trim();
|
|
196
|
+
if (!trimmed.startsWith("!")) return null;
|
|
197
|
+
const command = trimmed.slice(1).trim();
|
|
198
|
+
return command ? { command } : null;
|
|
199
|
+
}
|
|
200
|
+
function helpText() {
|
|
201
|
+
const usageWidth = Math.max(...SLASH_COMMANDS.map((command) => command.usage.length));
|
|
202
|
+
return ["Commands:", ...SLASH_COMMANDS.map((command) => `${command.usage.padEnd(usageWidth + 2)}${command.description}`)].join("\n");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ../terminal/src/ui/input.ts
|
|
206
|
+
var RawInput = class {
|
|
207
|
+
input;
|
|
208
|
+
onKey;
|
|
209
|
+
previousRawMode = false;
|
|
210
|
+
dataHandler = null;
|
|
211
|
+
constructor(input2, onKey) {
|
|
212
|
+
this.input = input2;
|
|
213
|
+
this.onKey = onKey;
|
|
214
|
+
}
|
|
215
|
+
start() {
|
|
216
|
+
this.previousRawMode = this.input.isRaw ?? false;
|
|
217
|
+
this.input.setRawMode(true);
|
|
218
|
+
this.input.resume();
|
|
219
|
+
this.dataHandler = (chunk) => {
|
|
220
|
+
for (const key of decodeKeys(chunk)) this.onKey(key);
|
|
221
|
+
};
|
|
222
|
+
this.input.on("data", this.dataHandler);
|
|
223
|
+
}
|
|
224
|
+
stop() {
|
|
225
|
+
if (this.dataHandler) this.input.off("data", this.dataHandler);
|
|
226
|
+
this.dataHandler = null;
|
|
227
|
+
this.input.setRawMode(this.previousRawMode);
|
|
228
|
+
this.input.pause();
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
function decodeKeys(chunk) {
|
|
232
|
+
const text = chunk.toString("utf8");
|
|
233
|
+
const keys = [];
|
|
234
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
235
|
+
const char = text[i];
|
|
236
|
+
const next = text[i + 1];
|
|
237
|
+
if (char === "\x1B") {
|
|
238
|
+
const sequence = readEscapeSequence(text, i);
|
|
239
|
+
keys.push(sequence.key);
|
|
240
|
+
i = sequence.end;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (char === "") keys.push("ctrl+c");
|
|
244
|
+
else if (char === "") keys.push("ctrl+d");
|
|
245
|
+
else if (char === "\f") keys.push("ctrl+l");
|
|
246
|
+
else if (char === "") keys.push("ctrl+a");
|
|
247
|
+
else if (char === "") keys.push("ctrl+e");
|
|
248
|
+
else if (char === "\v") keys.push("ctrl+k");
|
|
249
|
+
else if (char === "") keys.push("ctrl+u");
|
|
250
|
+
else if (char === "\x7F" || char === "\b") keys.push("backspace");
|
|
251
|
+
else if (char === "\r" || char === "\n") keys.push("enter");
|
|
252
|
+
else if (char >= " " || char && next) keys.push(char);
|
|
253
|
+
}
|
|
254
|
+
return keys;
|
|
255
|
+
}
|
|
256
|
+
function readEscapeSequence(text, start) {
|
|
257
|
+
const rest = text.slice(start);
|
|
258
|
+
if (rest.startsWith("\x1B\r") || rest.startsWith("\x1B\n")) return { key: "alt+enter", end: start + 1 };
|
|
259
|
+
if (rest.startsWith("\x1B[A")) return { key: "up", end: start + 2 };
|
|
260
|
+
if (rest.startsWith("\x1B[B")) return { key: "down", end: start + 2 };
|
|
261
|
+
if (rest.startsWith("\x1B[C")) return { key: "right", end: start + 2 };
|
|
262
|
+
if (rest.startsWith("\x1B[D")) return { key: "left", end: start + 2 };
|
|
263
|
+
if (rest.startsWith("\x1B[H") || rest.startsWith("\x1B[1~") || rest.startsWith("\x1BOH")) {
|
|
264
|
+
return { key: "home", end: start + (rest.startsWith("\x1B[1~") ? 3 : 2) };
|
|
265
|
+
}
|
|
266
|
+
if (rest.startsWith("\x1B[F") || rest.startsWith("\x1B[4~") || rest.startsWith("\x1BOF")) {
|
|
267
|
+
return { key: "end", end: start + (rest.startsWith("\x1B[4~") ? 3 : 2) };
|
|
268
|
+
}
|
|
269
|
+
if (rest.startsWith("\x1B[3~")) return { key: "delete", end: start + 3 };
|
|
270
|
+
if (rest.startsWith("\x1B[5~")) return { key: "pageup", end: start + 3 };
|
|
271
|
+
if (rest.startsWith("\x1B[6~")) return { key: "pagedown", end: start + 3 };
|
|
272
|
+
if (rest.length >= 2 && rest[1] && rest[1] >= " ") return { key: rest[1], end: start + 1 };
|
|
273
|
+
return { key: "", end: start };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ../terminal/src/ui/ansi.ts
|
|
277
|
+
var ESC = "\x1B[";
|
|
278
|
+
var COLORS = {
|
|
279
|
+
reset: "0",
|
|
280
|
+
muted: "38;5;244",
|
|
281
|
+
text: "38;5;252",
|
|
282
|
+
strong: "38;5;255",
|
|
283
|
+
accent: "38;5;81",
|
|
284
|
+
success: "38;5;114",
|
|
285
|
+
warning: "38;5;221",
|
|
286
|
+
error: "38;5;203",
|
|
287
|
+
dim: "38;5;240"
|
|
288
|
+
};
|
|
289
|
+
function color(name) {
|
|
290
|
+
return `${ESC}${COLORS[name]}m`;
|
|
291
|
+
}
|
|
292
|
+
function style(text, name) {
|
|
293
|
+
if (!text) return "";
|
|
294
|
+
return `${color(name)}${text}${color("reset")}`;
|
|
295
|
+
}
|
|
296
|
+
function surfaceLine(text, width, foreground = "strong") {
|
|
297
|
+
const padded = truncatePlain(text, Math.max(0, width)).padEnd(Math.max(0, width));
|
|
298
|
+
return `${ESC}48;5;236m${color(foreground)}${padded}${color("reset")}`;
|
|
299
|
+
}
|
|
300
|
+
function hideCursor() {
|
|
301
|
+
return "\x1B[?25l";
|
|
302
|
+
}
|
|
303
|
+
function showCursor() {
|
|
304
|
+
return "\x1B[?25h";
|
|
305
|
+
}
|
|
306
|
+
function setCursorColor(colorValue) {
|
|
307
|
+
return `\x1B]12;${colorValue}\x07`;
|
|
308
|
+
}
|
|
309
|
+
function resetCursorColor() {
|
|
310
|
+
return "\x1B]112\x07";
|
|
311
|
+
}
|
|
312
|
+
function clearLine() {
|
|
313
|
+
return `${ESC}2K`;
|
|
314
|
+
}
|
|
315
|
+
function truncatePlain(input2, max) {
|
|
316
|
+
if (max <= 0) return "";
|
|
317
|
+
const chars = Array.from(input2);
|
|
318
|
+
if (chars.length <= max) return input2;
|
|
319
|
+
if (max === 1) return ".";
|
|
320
|
+
return `${chars.slice(0, max - 1).join("")}.`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ../terminal/src/ui/layout.ts
|
|
324
|
+
function buildFrame(input2) {
|
|
325
|
+
const cols = Math.max(20, input2.cols);
|
|
326
|
+
const rows = Math.max(4, input2.rows);
|
|
327
|
+
const composer = renderComposer(input2.composer, cols, Math.min(5, rows - 1));
|
|
328
|
+
const menuRows = renderSlashMenu(input2.slashMenu, cols, Math.max(0, rows - composer.lines.length - 1));
|
|
329
|
+
const footer = renderFooter(input2.status, cols);
|
|
330
|
+
const fixedRows = 1 + composer.lines.length + menuRows.length;
|
|
331
|
+
const transcriptRows = Math.max(0, rows - fixedRows);
|
|
332
|
+
const transcriptLines = renderTranscript(input2.transcript, cols, input2.transcriptLayoutCache);
|
|
333
|
+
const start = Math.max(0, transcriptLines.length - transcriptRows - input2.scrollOffset);
|
|
334
|
+
const visibleTranscript = transcriptLines.length > 0 ? transcriptLines.slice(start, start + transcriptRows) : input2.status.running ? [style("Working...", "muted")] : [];
|
|
335
|
+
const spacer = visibleTranscript.length > 0 ? ["", ""] : [];
|
|
336
|
+
const lines = [...visibleTranscript, ...spacer, ...composer.lines, ...menuRows, footer].slice(0, rows);
|
|
337
|
+
const composerStartRow = visibleTranscript.length + spacer.length + 1;
|
|
338
|
+
return {
|
|
339
|
+
lines,
|
|
340
|
+
cursorRow: Math.min(lines.length, composerStartRow + composer.cursorLine),
|
|
341
|
+
cursorCol: Math.min(cols, composer.cursorCol)
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
function renderFooter(status, cols) {
|
|
345
|
+
const left = `${status.provider || "OpenPond"} / ${status.model || "OpenPond Chat"} ${compactPath(status.cwd)}`;
|
|
346
|
+
const right = status.notice ?? (status.running ? "running" : "/help");
|
|
347
|
+
const gap = Math.max(1, cols - visible(left) - visible(right));
|
|
348
|
+
return style(truncatePlain(`${left}${" ".repeat(gap)}${right}`, cols), "muted");
|
|
349
|
+
}
|
|
350
|
+
function renderTranscript(items, cols, cache) {
|
|
351
|
+
cache?.retain(new Set(items.map((item) => item.id)));
|
|
352
|
+
const lines = [];
|
|
353
|
+
for (const item of items) {
|
|
354
|
+
if (lines.length > 0) lines.push("");
|
|
355
|
+
lines.push(...renderItem(item, cols, cache));
|
|
356
|
+
}
|
|
357
|
+
return lines;
|
|
358
|
+
}
|
|
359
|
+
function renderTranscriptItemsForScrollback(items, cols) {
|
|
360
|
+
return renderTranscript(items, cols);
|
|
361
|
+
}
|
|
362
|
+
function renderWelcome(status, cols) {
|
|
363
|
+
const profile = status.profile || "none";
|
|
364
|
+
const agent = status.agent ? ` agent: ${status.agent}` : "";
|
|
365
|
+
const rows = [
|
|
366
|
+
"OpenPond",
|
|
367
|
+
`provider: ${status.provider || "OpenPond"} /provider`,
|
|
368
|
+
`model: ${status.model || "OpenPond Chat"} /model`,
|
|
369
|
+
`directory: ${compactPath(status.cwd)}`,
|
|
370
|
+
`profile: ${profile}${agent}`
|
|
371
|
+
];
|
|
372
|
+
const width = Math.min(cols, Math.max(34, ...rows.map((row) => visible(row) + 4)));
|
|
373
|
+
const horizontal = "\u2500".repeat(Math.max(0, width - 2));
|
|
374
|
+
return [
|
|
375
|
+
style(`\u250C${horizontal}\u2510`, "accent"),
|
|
376
|
+
...rows.map((row, index) => {
|
|
377
|
+
const padded = row.padEnd(Math.max(0, width - 4));
|
|
378
|
+
return `${style("\u2502", "accent")} ${style(padded, index === 0 ? "strong" : "text")} ${style("\u2502", "accent")}`;
|
|
379
|
+
}),
|
|
380
|
+
style(`\u2514${horizontal}\u2518`, "accent")
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
function renderItem(item, cols, cache) {
|
|
384
|
+
if (item.kind === "user") return renderUserMessage(item.text, cols);
|
|
385
|
+
if (item.kind === "assistant") {
|
|
386
|
+
return renderAssistantMessage(
|
|
387
|
+
item.text || (item.streaming ? "..." : ""),
|
|
388
|
+
cols,
|
|
389
|
+
cache ? (text, width) => cache.renderAssistant(item.id, text, width) : void 0
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
if (item.kind === "system") return renderBlock("!", item.text, cols, item.tone === "error" ? "error" : item.tone === "warning" ? "warning" : "muted", "muted");
|
|
393
|
+
if (item.kind === "approval") {
|
|
394
|
+
const body2 = `${item.title}${item.body ? `
|
|
395
|
+
${item.body}` : ""}`;
|
|
396
|
+
return renderBlock("?", body2, cols, "warning", "text");
|
|
397
|
+
}
|
|
398
|
+
if (item.kind === "command") {
|
|
399
|
+
const output2 = compactOutput(item.output);
|
|
400
|
+
const body2 = output2 ? `${item.title}
|
|
401
|
+
${output2}` : item.title;
|
|
402
|
+
return renderBlock(item.status === "failed" ? "!" : "$", body2, cols, item.status === "failed" ? "error" : "warning", "text");
|
|
403
|
+
}
|
|
404
|
+
const summary = compactOutput(item.summary);
|
|
405
|
+
const body = summary ? `${item.title}
|
|
406
|
+
${summary}` : item.title;
|
|
407
|
+
const label = item.status === "failed" ? "!" : item.status === "running" ? "*" : "ok";
|
|
408
|
+
return renderBlock(label, body, cols, item.status === "failed" ? "error" : item.status === "running" ? "warning" : "success", "text");
|
|
409
|
+
}
|
|
410
|
+
function renderUserMessage(body, cols) {
|
|
411
|
+
const bodyWidth = Math.max(12, cols - 4);
|
|
412
|
+
const bodyLines = body.split(/\r?\n/).flatMap((line) => wrapPlain(line, bodyWidth));
|
|
413
|
+
return [
|
|
414
|
+
surfaceLine("", cols),
|
|
415
|
+
...bodyLines.map((line) => surfaceLine(` ${line}`, cols)),
|
|
416
|
+
surfaceLine("", cols),
|
|
417
|
+
"",
|
|
418
|
+
""
|
|
419
|
+
];
|
|
420
|
+
}
|
|
421
|
+
function renderSlashMenu(menu, cols, maxRows) {
|
|
422
|
+
if (!menu || menu.items.length === 0 || maxRows <= 0) return [];
|
|
423
|
+
const visibleRows = Math.min(maxRows, 8, menu.items.length);
|
|
424
|
+
const selected = clamp(menu.selectedIndex, 0, menu.items.length - 1);
|
|
425
|
+
const start = Math.min(Math.max(0, selected - Math.floor(visibleRows / 2)), Math.max(0, menu.items.length - visibleRows));
|
|
426
|
+
const visibleItems = menu.items.slice(start, start + visibleRows);
|
|
427
|
+
const usageWidth = Math.min(24, Math.max(...visibleItems.map((item) => item.usage.length)));
|
|
428
|
+
return visibleItems.map((item, offset) => {
|
|
429
|
+
const index = start + offset;
|
|
430
|
+
const usage = item.usage.padEnd(usageWidth);
|
|
431
|
+
return surfaceLine(` ${usage} ${item.description}`, cols, index === selected ? "accent" : "text");
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
function renderAssistantMessage(body, cols, wrap) {
|
|
435
|
+
const lines = (wrap ? wrap(body, Math.max(8, cols)) : body.split(/\r?\n/).flatMap((line) => wrapPlain(line, Math.max(8, cols)))).map((line) => style(line, "text"));
|
|
436
|
+
return [...lines, ""];
|
|
437
|
+
}
|
|
438
|
+
function renderBlock(label, body, cols, labelColor, bodyColor) {
|
|
439
|
+
const labelWidth = Math.min(9, Math.max(2, label.length));
|
|
440
|
+
const bodyWidth = Math.max(8, cols - labelWidth - 2);
|
|
441
|
+
const bodyLines = body.split(/\r?\n/).flatMap((line) => wrapPlain(line, bodyWidth));
|
|
442
|
+
const first = bodyLines[0] ?? "";
|
|
443
|
+
const lines = [`${style(label.padEnd(labelWidth), labelColor)} ${style(first, bodyColor)}`];
|
|
444
|
+
for (const line of bodyLines.slice(1)) {
|
|
445
|
+
lines.push(`${" ".repeat(labelWidth)} ${style(line, bodyColor)}`);
|
|
446
|
+
}
|
|
447
|
+
return lines;
|
|
448
|
+
}
|
|
449
|
+
function compactPath(cwd) {
|
|
450
|
+
const normalized = cwd.replace(/\\/g, "/");
|
|
451
|
+
const home = process.env.HOME?.replace(/\\/g, "/");
|
|
452
|
+
if (home && normalized === home) return "~";
|
|
453
|
+
if (home && normalized.startsWith(`${home}/`)) return `~/${normalized.slice(home.length + 1)}`;
|
|
454
|
+
return normalized;
|
|
455
|
+
}
|
|
456
|
+
function compactOutput(output2) {
|
|
457
|
+
const trimmed = output2.trim();
|
|
458
|
+
if (!trimmed) return "";
|
|
459
|
+
const lines = trimmed.split(/\r?\n/);
|
|
460
|
+
const maxLines = 8;
|
|
461
|
+
const clipped = lines.length > maxLines ? [...lines.slice(0, maxLines - 1), `... ${lines.length - maxLines + 1} more lines`] : lines;
|
|
462
|
+
return clipped.map((line) => truncatePlain(line, 160)).join("\n");
|
|
463
|
+
}
|
|
464
|
+
function renderComposer(composer, cols, maxLines) {
|
|
465
|
+
const width = Math.max(1, cols - 4);
|
|
466
|
+
const wrapped = wrapComposerText(composer.text, composer.cursor, width);
|
|
467
|
+
const maxTextLines = Math.max(1, maxLines - 2);
|
|
468
|
+
const start = Math.max(0, wrapped.cursorLine - maxTextLines + 1);
|
|
469
|
+
const visibleLines = wrapped.lines.slice(start, start + maxLines);
|
|
470
|
+
const renderedText = visibleLines.slice(0, maxTextLines).map((line) => {
|
|
471
|
+
const text = line || (composer.text ? "" : " message OpenPond or /help");
|
|
472
|
+
return surfaceLine(` ${text}`, cols, line ? "strong" : "dim");
|
|
473
|
+
});
|
|
474
|
+
if (renderedText.length === 0) renderedText.push(surfaceLine(" message OpenPond or /help", cols, "dim"));
|
|
475
|
+
const cursorLine = Math.max(0, wrapped.cursorLine - start);
|
|
476
|
+
return {
|
|
477
|
+
lines: [surfaceLine("", cols), ...renderedText, surfaceLine("", cols)],
|
|
478
|
+
cursorLine: cursorLine + 1,
|
|
479
|
+
cursorCol: 3 + wrapped.cursorCol
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function wrapComposerText(text, cursor, width) {
|
|
483
|
+
const lines = [""];
|
|
484
|
+
let line = 0;
|
|
485
|
+
let col = 0;
|
|
486
|
+
let cursorLine = 0;
|
|
487
|
+
let cursorCol = 0;
|
|
488
|
+
const chars = Array.from(text);
|
|
489
|
+
for (let i = 0; i <= chars.length; i += 1) {
|
|
490
|
+
if (i === cursor) {
|
|
491
|
+
cursorLine = line;
|
|
492
|
+
cursorCol = col;
|
|
493
|
+
}
|
|
494
|
+
if (i === chars.length) break;
|
|
495
|
+
const char = chars[i];
|
|
496
|
+
if (char === "\n" || col >= width) {
|
|
497
|
+
line += 1;
|
|
498
|
+
col = 0;
|
|
499
|
+
lines[line] = "";
|
|
500
|
+
if (char === "\n") continue;
|
|
501
|
+
}
|
|
502
|
+
lines[line] += char;
|
|
503
|
+
col += 1;
|
|
504
|
+
}
|
|
505
|
+
return { lines, cursorLine, cursorCol };
|
|
506
|
+
}
|
|
507
|
+
function wrapPlain(input2, width) {
|
|
508
|
+
if (!input2) return [""];
|
|
509
|
+
const words = input2.split(/(\s+)/);
|
|
510
|
+
const lines = [];
|
|
511
|
+
let current = "";
|
|
512
|
+
for (const word of words) {
|
|
513
|
+
if (!word) continue;
|
|
514
|
+
if (visible(current) + visible(word) <= width) {
|
|
515
|
+
current += word;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (current.trimEnd()) lines.push(current.trimEnd());
|
|
519
|
+
current = "";
|
|
520
|
+
if (visible(word) > width) {
|
|
521
|
+
const chars = Array.from(word);
|
|
522
|
+
while (chars.length > width) lines.push(chars.splice(0, width).join(""));
|
|
523
|
+
current = chars.join("");
|
|
524
|
+
} else {
|
|
525
|
+
current = word.trimStart();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (current.trimEnd()) lines.push(current.trimEnd());
|
|
529
|
+
return lines.length ? lines : [""];
|
|
530
|
+
}
|
|
531
|
+
function visible(input2) {
|
|
532
|
+
return Array.from(input2).length;
|
|
533
|
+
}
|
|
534
|
+
function clamp(value, min, max) {
|
|
535
|
+
return Math.min(max, Math.max(min, value));
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ../terminal/src/ui/render-scheduler.ts
|
|
539
|
+
var DEFAULT_MAX_FPS = 30;
|
|
540
|
+
function createTerminalRenderScheduler(render, options = {}) {
|
|
541
|
+
const maxFps = Number.isFinite(options.maxFps) && options.maxFps > 0 ? options.maxFps : DEFAULT_MAX_FPS;
|
|
542
|
+
const frameMs = Math.max(1, Math.ceil(1e3 / maxFps));
|
|
543
|
+
const setTimer = options.setTimer ?? setTimeout;
|
|
544
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
545
|
+
let timer = null;
|
|
546
|
+
let pending = false;
|
|
547
|
+
function clearPendingTimer() {
|
|
548
|
+
if (!timer) return;
|
|
549
|
+
clearTimer(timer);
|
|
550
|
+
timer = null;
|
|
551
|
+
}
|
|
552
|
+
function paint() {
|
|
553
|
+
timer = null;
|
|
554
|
+
if (!pending) return;
|
|
555
|
+
pending = false;
|
|
556
|
+
render();
|
|
557
|
+
}
|
|
558
|
+
return {
|
|
559
|
+
request() {
|
|
560
|
+
pending = true;
|
|
561
|
+
if (timer) return;
|
|
562
|
+
timer = setTimer(paint, frameMs);
|
|
563
|
+
},
|
|
564
|
+
flush() {
|
|
565
|
+
if (!pending) {
|
|
566
|
+
clearPendingTimer();
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
pending = false;
|
|
570
|
+
clearPendingTimer();
|
|
571
|
+
render();
|
|
572
|
+
},
|
|
573
|
+
cancel() {
|
|
574
|
+
pending = false;
|
|
575
|
+
clearPendingTimer();
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ../terminal/src/ui/transcript-layout-cache.ts
|
|
581
|
+
var TranscriptLayoutCache = class {
|
|
582
|
+
#assistant = /* @__PURE__ */ new Map();
|
|
583
|
+
#processedCharacters = 0;
|
|
584
|
+
renderAssistant(id, text, width) {
|
|
585
|
+
const safeWidth = Math.max(1, width);
|
|
586
|
+
let entry = this.#assistant.get(id);
|
|
587
|
+
if (!entry || entry.width !== safeWidth || !text.startsWith(entry.lastText)) {
|
|
588
|
+
entry = {
|
|
589
|
+
width: safeWidth,
|
|
590
|
+
lastText: "",
|
|
591
|
+
stableOffset: 0,
|
|
592
|
+
stableState: { lines: [], current: "", trailingNewline: false }
|
|
593
|
+
};
|
|
594
|
+
this.#assistant.set(id, entry);
|
|
595
|
+
}
|
|
596
|
+
const suffix = text.slice(entry.stableOffset);
|
|
597
|
+
const stableLength = stablePrefixLength(suffix, safeWidth);
|
|
598
|
+
if (stableLength > 0) {
|
|
599
|
+
this.#process(entry.stableState, suffix.slice(0, stableLength), safeWidth);
|
|
600
|
+
entry.stableOffset += stableLength;
|
|
601
|
+
}
|
|
602
|
+
const rendered = cloneState(entry.stableState);
|
|
603
|
+
this.#process(rendered, text.slice(entry.stableOffset), safeWidth);
|
|
604
|
+
entry.lastText = text;
|
|
605
|
+
return finishState(rendered);
|
|
606
|
+
}
|
|
607
|
+
retain(itemIds) {
|
|
608
|
+
for (const id of this.#assistant.keys()) {
|
|
609
|
+
if (!itemIds.has(id)) this.#assistant.delete(id);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
stats() {
|
|
613
|
+
return { entries: this.#assistant.size, processedCharacters: this.#processedCharacters };
|
|
614
|
+
}
|
|
615
|
+
#process(state, input2, width) {
|
|
616
|
+
this.#processedCharacters += input2.length;
|
|
617
|
+
for (const token of input2.match(/\r\n|\r|\n|[^\S\r\n]+|\S+/g) ?? []) {
|
|
618
|
+
if (/^\r?\n$|^\r$/.test(token)) processNewline(state);
|
|
619
|
+
else processToken(state, token, width);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
function stablePrefixLength(input2, width) {
|
|
624
|
+
const matches = [...input2.matchAll(/\r\n|\r|\n|[^\S\r\n]+|\S+/g)];
|
|
625
|
+
if (matches.length === 0) return 0;
|
|
626
|
+
const last = matches.at(-1);
|
|
627
|
+
const lastStart = last.index ?? 0;
|
|
628
|
+
const lastToken = last[0];
|
|
629
|
+
if (/^\r?\n$|^\r$/.test(lastToken)) return input2.length;
|
|
630
|
+
if (/^\s+$/.test(lastToken)) return lastStart;
|
|
631
|
+
const stableWordCharacters = Math.max(0, Math.floor((lastToken.length - 1) / width) * width);
|
|
632
|
+
return lastStart + stableWordCharacters;
|
|
633
|
+
}
|
|
634
|
+
function processToken(state, token, width) {
|
|
635
|
+
state.trailingNewline = false;
|
|
636
|
+
if (visible2(state.current) + visible2(token) <= width) {
|
|
637
|
+
state.current += token;
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (state.current.trimEnd()) state.lines.push(state.current.trimEnd());
|
|
641
|
+
state.current = "";
|
|
642
|
+
if (visible2(token) > width) {
|
|
643
|
+
const chars = Array.from(token);
|
|
644
|
+
while (chars.length > width) state.lines.push(chars.splice(0, width).join(""));
|
|
645
|
+
state.current = chars.join("");
|
|
646
|
+
} else {
|
|
647
|
+
state.current = token.trimStart();
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function processNewline(state) {
|
|
651
|
+
state.lines.push(state.current.trimEnd());
|
|
652
|
+
state.current = "";
|
|
653
|
+
state.trailingNewline = true;
|
|
654
|
+
}
|
|
655
|
+
function finishState(state) {
|
|
656
|
+
const lines = [...state.lines];
|
|
657
|
+
if (state.current.trimEnd()) lines.push(state.current.trimEnd());
|
|
658
|
+
else if (state.trailingNewline) lines.push("");
|
|
659
|
+
return lines.length > 0 ? lines : [""];
|
|
660
|
+
}
|
|
661
|
+
function cloneState(state) {
|
|
662
|
+
return { lines: [...state.lines], current: state.current, trailingNewline: state.trailingNewline };
|
|
663
|
+
}
|
|
664
|
+
function visible2(input2) {
|
|
665
|
+
return Array.from(input2).length;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ../terminal/src/ui/renderer.ts
|
|
669
|
+
var CSI = "\x1B[";
|
|
670
|
+
var MAX_FRAME_ROWS = 10;
|
|
671
|
+
var TerminalRenderer = class {
|
|
672
|
+
output;
|
|
673
|
+
previousRows = 0;
|
|
674
|
+
cursorRow = 1;
|
|
675
|
+
active = false;
|
|
676
|
+
resizeHandler = null;
|
|
677
|
+
transcriptLayoutCache = new TranscriptLayoutCache();
|
|
678
|
+
constructor(output2, onResize) {
|
|
679
|
+
this.output = output2;
|
|
680
|
+
this.resizeHandler = onResize;
|
|
681
|
+
}
|
|
682
|
+
start() {
|
|
683
|
+
if (this.active) return;
|
|
684
|
+
this.active = true;
|
|
685
|
+
this.output.write(`${setCursorColor("#ffffff")}${hideCursor()}`);
|
|
686
|
+
if (this.resizeHandler) this.output.on("resize", this.resizeHandler);
|
|
687
|
+
}
|
|
688
|
+
stop() {
|
|
689
|
+
if (!this.active) return;
|
|
690
|
+
if (this.resizeHandler) this.output.off("resize", this.resizeHandler);
|
|
691
|
+
this.output.write(`${this.clearPrompt()}${resetCursorColor()}${showCursor()}`);
|
|
692
|
+
this.active = false;
|
|
693
|
+
}
|
|
694
|
+
fullRedraw() {
|
|
695
|
+
}
|
|
696
|
+
render(input2) {
|
|
697
|
+
if (!this.active) return;
|
|
698
|
+
const cols = this.output.columns || 80;
|
|
699
|
+
const rows = Math.min(this.output.rows || 24, MAX_FRAME_ROWS);
|
|
700
|
+
const frame = buildFrame({ ...input2, cols, rows, transcriptLayoutCache: this.transcriptLayoutCache });
|
|
701
|
+
let buffer = `${hideCursor()}${this.clearPrompt()}`;
|
|
702
|
+
const physicalRows = frame.lines.length;
|
|
703
|
+
for (let index = 0; index < physicalRows; index += 1) {
|
|
704
|
+
buffer += `${clearLine()}${frame.lines[index] ?? ""}`;
|
|
705
|
+
if (index < physicalRows - 1) buffer += "\n";
|
|
706
|
+
}
|
|
707
|
+
const rowsUp = Math.max(0, physicalRows - frame.cursorRow);
|
|
708
|
+
buffer += rowsUp > 0 ? `${CSI}${rowsUp}F` : "\r";
|
|
709
|
+
if (frame.cursorCol > 1) buffer += `${CSI}${frame.cursorCol - 1}C`;
|
|
710
|
+
buffer += showCursor();
|
|
711
|
+
this.previousRows = physicalRows;
|
|
712
|
+
this.cursorRow = frame.cursorRow;
|
|
713
|
+
this.output.write(buffer);
|
|
714
|
+
}
|
|
715
|
+
commitLines(lines) {
|
|
716
|
+
if (!this.active || lines.length === 0) return;
|
|
717
|
+
let buffer = `${hideCursor()}${this.clearPrompt()}`;
|
|
718
|
+
buffer += lines.map((line) => `${line}\r
|
|
719
|
+
`).join("");
|
|
720
|
+
this.output.write(buffer);
|
|
721
|
+
}
|
|
722
|
+
clearPrompt() {
|
|
723
|
+
if (this.previousRows === 0) return "";
|
|
724
|
+
let buffer = this.cursorRow > 1 ? `${CSI}${this.cursorRow - 1}F` : "\r";
|
|
725
|
+
for (let index = 0; index < this.previousRows; index += 1) {
|
|
726
|
+
buffer += clearLine();
|
|
727
|
+
if (index < this.previousRows - 1) buffer += "\n";
|
|
728
|
+
}
|
|
729
|
+
if (this.previousRows > 1) buffer += `${CSI}${this.previousRows - 1}F`;
|
|
730
|
+
else buffer += "\r";
|
|
731
|
+
this.previousRows = 0;
|
|
732
|
+
this.cursorRow = 1;
|
|
733
|
+
return buffer;
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// ../terminal/src/ui/transcript.ts
|
|
738
|
+
var MAX_ACTIVE_TRANSCRIPT_ITEMS = 200;
|
|
739
|
+
var MAX_ACTIVE_STREAMING_TEXT_BYTES = 128 * 1024;
|
|
740
|
+
var STREAM_TRUNCATION_MARKER = "\n\n[terminal live output truncated; full output remains available from session history]";
|
|
741
|
+
function itemId(prefix) {
|
|
742
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
743
|
+
}
|
|
744
|
+
function nowIso() {
|
|
745
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
746
|
+
}
|
|
747
|
+
function userItem(text) {
|
|
748
|
+
return { id: itemId("user"), kind: "user", text, createdAt: nowIso() };
|
|
749
|
+
}
|
|
750
|
+
function systemItem(text, tone = "info") {
|
|
751
|
+
return { id: itemId("system"), kind: "system", tone, text, createdAt: nowIso() };
|
|
752
|
+
}
|
|
753
|
+
function isTranscriptItemCommitReady(item) {
|
|
754
|
+
if (item.kind === "assistant") return !item.streaming;
|
|
755
|
+
if (item.kind === "command" || item.kind === "tool") return item.status !== "running";
|
|
756
|
+
if (item.kind === "approval") return item.status !== "pending";
|
|
757
|
+
return true;
|
|
758
|
+
}
|
|
759
|
+
function limitActiveTranscriptItems(items, limit = MAX_ACTIVE_TRANSCRIPT_ITEMS) {
|
|
760
|
+
if (items.length <= limit) return items;
|
|
761
|
+
return items.slice(Math.max(0, items.length - limit));
|
|
762
|
+
}
|
|
763
|
+
function appendTranscriptItem(items, item, limit = MAX_ACTIVE_TRANSCRIPT_ITEMS) {
|
|
764
|
+
return limitActiveTranscriptItems([...items, item], limit);
|
|
765
|
+
}
|
|
766
|
+
function commitReadyTranscriptItems(items, isReady = isTranscriptItemCommitReady) {
|
|
767
|
+
const readyItems = [];
|
|
768
|
+
const activeItems = [];
|
|
769
|
+
for (const item of items) {
|
|
770
|
+
if (isReady(item)) readyItems.push(item);
|
|
771
|
+
else activeItems.push(item);
|
|
772
|
+
}
|
|
773
|
+
return { readyItems, activeItems: limitActiveTranscriptItems(activeItems) };
|
|
774
|
+
}
|
|
775
|
+
function asRecord(value) {
|
|
776
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
777
|
+
}
|
|
778
|
+
function stringValue(record, keys) {
|
|
779
|
+
if (!record) return null;
|
|
780
|
+
for (const key of keys) {
|
|
781
|
+
const value = record[key];
|
|
782
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
783
|
+
}
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
function approvalRequestedId(event) {
|
|
787
|
+
return stringValue(asRecord(event.data), ["id", "approvalId", "approval_id"]) ?? event.id;
|
|
788
|
+
}
|
|
789
|
+
function approvalResolvedId(event) {
|
|
790
|
+
return stringValue(asRecord(event.data), ["approvalId", "approval_id", "id"]) ?? event.id;
|
|
791
|
+
}
|
|
792
|
+
function toolEventId(event) {
|
|
793
|
+
const data = asRecord(event.data);
|
|
794
|
+
const stableId = stringValue(data, [
|
|
795
|
+
"toolCallId",
|
|
796
|
+
"tool_call_id",
|
|
797
|
+
"workspaceToolCallId",
|
|
798
|
+
"workspace_tool_call_id",
|
|
799
|
+
"callId",
|
|
800
|
+
"call_id",
|
|
801
|
+
"itemId",
|
|
802
|
+
"item_id",
|
|
803
|
+
"id"
|
|
804
|
+
]);
|
|
805
|
+
return stableId ? `${event.name.startsWith("workspace_") ? "workspace" : "tool"}-${stableId}` : event.id;
|
|
806
|
+
}
|
|
807
|
+
function appendRuntimeEvent(items, event) {
|
|
808
|
+
if (event.name === "assistant.delta") {
|
|
809
|
+
const delta = event.output ?? "";
|
|
810
|
+
if (!delta) return items;
|
|
811
|
+
const last = items[items.length - 1];
|
|
812
|
+
if (last?.kind === "assistant" && last.streaming) {
|
|
813
|
+
if (last.truncated) return items;
|
|
814
|
+
const appended = appendStreamingText(last.text, last.textBytes, delta);
|
|
815
|
+
return limitActiveTranscriptItems([
|
|
816
|
+
...items.slice(0, -1),
|
|
817
|
+
{
|
|
818
|
+
...last,
|
|
819
|
+
text: appended.text,
|
|
820
|
+
textBytes: appended.textBytes,
|
|
821
|
+
truncated: appended.truncated
|
|
822
|
+
}
|
|
823
|
+
]);
|
|
824
|
+
}
|
|
825
|
+
const initial = appendStreamingText("", 0, delta);
|
|
826
|
+
return appendTranscriptItem(
|
|
827
|
+
items,
|
|
828
|
+
{
|
|
829
|
+
id: event.turnId ? `assistant-${event.turnId}` : itemId("assistant"),
|
|
830
|
+
kind: "assistant",
|
|
831
|
+
text: initial.text,
|
|
832
|
+
textBytes: initial.textBytes,
|
|
833
|
+
truncated: initial.truncated,
|
|
834
|
+
streaming: true,
|
|
835
|
+
createdAt: event.timestamp
|
|
836
|
+
}
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
if (event.name === "turn.completed") {
|
|
840
|
+
return limitActiveTranscriptItems(
|
|
841
|
+
items.map((item) => item.kind === "assistant" && item.streaming ? { ...item, streaming: false } : item)
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
if (event.name === "turn.failed" || event.name === "turn.interrupted") {
|
|
845
|
+
const tone = event.name === "turn.failed" ? "error" : "warning";
|
|
846
|
+
const text = event.error || event.output || (event.name === "turn.failed" ? "Turn failed" : "Turn interrupted");
|
|
847
|
+
return appendTranscriptItem(
|
|
848
|
+
items.map((item) => item.kind === "assistant" && item.streaming ? { ...item, streaming: false } : item),
|
|
849
|
+
systemItem(text, tone)
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
if (event.name === "approval.requested") {
|
|
853
|
+
return appendTranscriptItem(
|
|
854
|
+
items,
|
|
855
|
+
{
|
|
856
|
+
id: approvalRequestedId(event),
|
|
857
|
+
kind: "approval",
|
|
858
|
+
title: event.action || "Approval requested",
|
|
859
|
+
body: event.output || "Review the approval request in the app.",
|
|
860
|
+
status: "pending",
|
|
861
|
+
createdAt: event.timestamp
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
if (event.name === "approval.resolved") {
|
|
866
|
+
const approvalId = approvalResolvedId(event);
|
|
867
|
+
return limitActiveTranscriptItems(
|
|
868
|
+
items.map(
|
|
869
|
+
(item) => item.kind === "approval" && item.id === approvalId ? { ...item, status: event.status === "completed" ? "approved" : "denied" } : item
|
|
870
|
+
)
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
if (event.name === "command.output") {
|
|
874
|
+
return appendTranscriptItem(
|
|
875
|
+
items,
|
|
876
|
+
{
|
|
877
|
+
id: event.id,
|
|
878
|
+
kind: "command",
|
|
879
|
+
title: event.action || "command",
|
|
880
|
+
output: event.output || "",
|
|
881
|
+
status: event.error ? "failed" : "succeeded",
|
|
882
|
+
createdAt: event.timestamp
|
|
883
|
+
}
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
if (event.name === "tool.started" || event.name === "workspace_action") {
|
|
887
|
+
return appendTranscriptItem(
|
|
888
|
+
items,
|
|
889
|
+
{
|
|
890
|
+
id: toolEventId(event),
|
|
891
|
+
kind: "tool",
|
|
892
|
+
title: event.action || "tool",
|
|
893
|
+
summary: event.output || "",
|
|
894
|
+
status: "running",
|
|
895
|
+
createdAt: event.timestamp
|
|
896
|
+
}
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
if (event.name === "tool.completed" || event.name === "workspace_action_result") {
|
|
900
|
+
const nextItem = {
|
|
901
|
+
id: toolEventId(event),
|
|
902
|
+
kind: "tool",
|
|
903
|
+
title: event.action || "tool",
|
|
904
|
+
summary: event.error || event.output || "",
|
|
905
|
+
status: event.error || event.status === "failed" ? "failed" : "succeeded",
|
|
906
|
+
createdAt: event.timestamp
|
|
907
|
+
};
|
|
908
|
+
const index = items.findIndex((item) => item.kind === "tool" && item.id === nextItem.id);
|
|
909
|
+
if (index === -1) return appendTranscriptItem(items, nextItem);
|
|
910
|
+
return limitActiveTranscriptItems([...items.slice(0, index), nextItem, ...items.slice(index + 1)]);
|
|
911
|
+
}
|
|
912
|
+
if (event.name === "session.compaction.started") return appendTranscriptItem(items, systemItem("Compaction started"));
|
|
913
|
+
if (event.name === "session.compaction.completed") return appendTranscriptItem(items, systemItem("Compaction completed"));
|
|
914
|
+
if (event.name === "session.compaction.failed") return appendTranscriptItem(items, systemItem(event.error || "Compaction failed", "error"));
|
|
915
|
+
return items;
|
|
916
|
+
}
|
|
917
|
+
function appendStreamingText(current, knownCurrentBytes, delta) {
|
|
918
|
+
const currentBytes = knownCurrentBytes ?? utf8Bytes(current);
|
|
919
|
+
const deltaBytes = utf8Bytes(delta);
|
|
920
|
+
if (currentBytes + deltaBytes <= MAX_ACTIVE_STREAMING_TEXT_BYTES) {
|
|
921
|
+
return { text: `${current}${delta}`, textBytes: currentBytes + deltaBytes, truncated: false };
|
|
922
|
+
}
|
|
923
|
+
const markerBytes = utf8Bytes(STREAM_TRUNCATION_MARKER);
|
|
924
|
+
const remainingBytes = Math.max(0, MAX_ACTIVE_STREAMING_TEXT_BYTES - currentBytes - markerBytes);
|
|
925
|
+
const prefix = utf8Prefix(delta, remainingBytes);
|
|
926
|
+
const text = `${current}${prefix}${STREAM_TRUNCATION_MARKER}`;
|
|
927
|
+
return { text, textBytes: utf8Bytes(text), truncated: true };
|
|
928
|
+
}
|
|
929
|
+
function utf8Prefix(value, maxBytes) {
|
|
930
|
+
if (maxBytes <= 0) return "";
|
|
931
|
+
let output2 = "";
|
|
932
|
+
let bytes = 0;
|
|
933
|
+
for (const char of value) {
|
|
934
|
+
const size = utf8Bytes(char);
|
|
935
|
+
if (bytes + size > maxBytes) break;
|
|
936
|
+
output2 += char;
|
|
937
|
+
bytes += size;
|
|
938
|
+
}
|
|
939
|
+
return output2;
|
|
940
|
+
}
|
|
941
|
+
function utf8Bytes(value) {
|
|
942
|
+
return new TextEncoder().encode(value).byteLength;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// ../terminal/src/args.ts
|
|
946
|
+
import path from "node:path";
|
|
947
|
+
|
|
948
|
+
// ../terminal/src/formatting.ts
|
|
949
|
+
var TERMINAL_MODEL_OPTIONS = [
|
|
950
|
+
{
|
|
951
|
+
id: DEFAULT_OPENPOND_CHAT_MODEL,
|
|
952
|
+
label: "OpenPond Chat",
|
|
953
|
+
description: "default OpenPond hosted chat model"
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
id: "deepseek-v4-flash",
|
|
957
|
+
label: "DeepSeek V4 Flash",
|
|
958
|
+
description: "fast OpenPond-compatible model"
|
|
959
|
+
}
|
|
960
|
+
];
|
|
961
|
+
var OPENAI_COMPATIBLE_CHAT_PROVIDER_IDS = [
|
|
962
|
+
"local-adapter",
|
|
963
|
+
"openai",
|
|
964
|
+
"xai",
|
|
965
|
+
"openrouter",
|
|
966
|
+
"deepseek",
|
|
967
|
+
"zai",
|
|
968
|
+
"moonshot",
|
|
969
|
+
"together",
|
|
970
|
+
"groq",
|
|
971
|
+
"fireworks",
|
|
972
|
+
"custom-openai-compatible"
|
|
973
|
+
];
|
|
974
|
+
var RUNNABLE_CHAT_PROVIDER_IDS = [
|
|
975
|
+
"openpond",
|
|
976
|
+
"codex",
|
|
977
|
+
...OPENAI_COMPATIBLE_CHAT_PROVIDER_IDS
|
|
978
|
+
];
|
|
979
|
+
var RUNNABLE_CHAT_PROVIDER_ID_SET = new Set(
|
|
980
|
+
RUNNABLE_CHAT_PROVIDER_IDS
|
|
981
|
+
);
|
|
982
|
+
function normalizeTerminalProvider(value) {
|
|
983
|
+
const normalized = value?.trim();
|
|
984
|
+
if (!normalized) return null;
|
|
985
|
+
if (RUNNABLE_CHAT_PROVIDER_ID_SET.has(normalized)) {
|
|
986
|
+
return normalized;
|
|
987
|
+
}
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
function parseProviderModelSelection(value, currentProvider) {
|
|
991
|
+
const normalized = value?.trim();
|
|
992
|
+
if (!normalized) return { provider: null, model: null };
|
|
993
|
+
const slashIndex = normalized.indexOf("/");
|
|
994
|
+
if (slashIndex > 0) {
|
|
995
|
+
const provider = normalizeTerminalProvider(normalized.slice(0, slashIndex));
|
|
996
|
+
if (provider) {
|
|
997
|
+
return {
|
|
998
|
+
provider,
|
|
999
|
+
model: normalizeTerminalModel(normalized.slice(slashIndex + 1))
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return {
|
|
1004
|
+
provider: currentProvider,
|
|
1005
|
+
model: normalizeTerminalModel(normalized)
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function normalizeTerminalModel(value) {
|
|
1009
|
+
const normalized = value?.trim();
|
|
1010
|
+
if (!normalized) return null;
|
|
1011
|
+
const lower = normalized.toLowerCase();
|
|
1012
|
+
if (lower === "default" || lower === "openpond chat" || lower === DEFAULT_OPENPOND_CHAT_MODEL) {
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
return normalized;
|
|
1016
|
+
}
|
|
1017
|
+
function providerLabel(settings, provider) {
|
|
1018
|
+
if (provider === "openpond") return "OpenPond";
|
|
1019
|
+
return settings?.statuses[provider]?.displayName ?? (provider === "codex" ? "Codex" : provider);
|
|
1020
|
+
}
|
|
1021
|
+
function modelOptionFromProviderModel(model) {
|
|
1022
|
+
const details = [
|
|
1023
|
+
model.contextWindow ? `${Math.round(model.contextWindow / 1e3)}K context` : "",
|
|
1024
|
+
model.capabilities.reasoning ? "reasoning" : "",
|
|
1025
|
+
model.source
|
|
1026
|
+
].filter(Boolean);
|
|
1027
|
+
return {
|
|
1028
|
+
id: model.id,
|
|
1029
|
+
label: model.displayName || model.id,
|
|
1030
|
+
description: details.join(", ") || model.providerId
|
|
1031
|
+
};
|
|
1032
|
+
}
|
|
1033
|
+
function modelOptionsForProvider(settings, provider) {
|
|
1034
|
+
if (provider === "openpond") {
|
|
1035
|
+
return TERMINAL_MODEL_OPTIONS.filter((option) => option.id === DEFAULT_OPENPOND_CHAT_MODEL);
|
|
1036
|
+
}
|
|
1037
|
+
if (provider === "codex") {
|
|
1038
|
+
return [
|
|
1039
|
+
{ id: DEFAULT_CODEX_CHAT_MODEL, label: "GPT-5.6 Sol", description: "default Codex model" },
|
|
1040
|
+
{ id: "gpt-5.6-terra", label: "GPT-5.6 Terra", description: "Codex model" },
|
|
1041
|
+
{ id: "gpt-5.6-luna", label: "GPT-5.6 Luna", description: "Codex model" },
|
|
1042
|
+
{ id: "gpt-5.5", label: "GPT-5.5", description: "Codex model" },
|
|
1043
|
+
{ id: "gpt-5.4", label: "GPT-5.4", description: "Codex model" },
|
|
1044
|
+
{ id: "gpt-5.4-mini", label: "GPT-5.4 Mini", description: "Codex model" },
|
|
1045
|
+
{ id: "gpt-5.3-codex", label: "GPT-5.3 Codex", description: "Codex model" },
|
|
1046
|
+
{ id: "gpt-5.3-codex-spark", label: "GPT-5.3 Codex Spark", description: "Codex model" },
|
|
1047
|
+
{ id: "gpt-5.2", label: "GPT-5.2", description: "Codex model" }
|
|
1048
|
+
];
|
|
1049
|
+
}
|
|
1050
|
+
const cacheModels = settings?.modelCaches[provider]?.models ?? [];
|
|
1051
|
+
const config = settings?.providers[provider];
|
|
1052
|
+
const manualModels = [...config?.modelOverrides ?? [], config?.defaultModel].filter((value) => Boolean(value?.trim())).map((id) => ({ id, label: id, description: "manual" }));
|
|
1053
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1054
|
+
const options = [];
|
|
1055
|
+
for (const option of [...cacheModels.map(modelOptionFromProviderModel), ...manualModels]) {
|
|
1056
|
+
if (!option.id || seen.has(option.id)) continue;
|
|
1057
|
+
seen.add(option.id);
|
|
1058
|
+
options.push(option);
|
|
1059
|
+
}
|
|
1060
|
+
return options;
|
|
1061
|
+
}
|
|
1062
|
+
function defaultModelId(settings, provider) {
|
|
1063
|
+
const configDefault = settings?.providers[provider]?.defaultModel?.trim();
|
|
1064
|
+
if (configDefault) return configDefault;
|
|
1065
|
+
const statusDefault = settings?.statuses[provider]?.defaultModel?.trim();
|
|
1066
|
+
if (statusDefault) return statusDefault;
|
|
1067
|
+
const firstOption = modelOptionsForProvider(settings, provider)[0]?.id?.trim();
|
|
1068
|
+
if (firstOption) return firstOption;
|
|
1069
|
+
if (provider === "codex") return DEFAULT_CODEX_CHAT_MODEL;
|
|
1070
|
+
if (provider === "openpond") return DEFAULT_OPENPOND_CHAT_MODEL;
|
|
1071
|
+
return "";
|
|
1072
|
+
}
|
|
1073
|
+
function activeModelId(options, settings) {
|
|
1074
|
+
const normalized = normalizeTerminalModel(options.model);
|
|
1075
|
+
if (normalized) return normalized;
|
|
1076
|
+
return defaultModelId(settings, options.provider);
|
|
1077
|
+
}
|
|
1078
|
+
function activeModelRef(options, settings) {
|
|
1079
|
+
const modelId = activeModelId(options, settings);
|
|
1080
|
+
return modelId ? { providerId: options.provider, modelId } : void 0;
|
|
1081
|
+
}
|
|
1082
|
+
function modelLabel(settings, options) {
|
|
1083
|
+
const id = activeModelId(options, settings);
|
|
1084
|
+
return modelOptionsForProvider(settings, options.provider).find((option) => option.id === id)?.label ?? (id || "No model");
|
|
1085
|
+
}
|
|
1086
|
+
function terminalSessionHeading(settings, options) {
|
|
1087
|
+
const provider = providerLabel(settings, options.provider);
|
|
1088
|
+
const productAndProvider = options.provider === "openpond" ? "OpenPond" : `OpenPond ${provider}`;
|
|
1089
|
+
return `${productAndProvider} / ${modelLabel(settings, options)} ${options.cwd}`;
|
|
1090
|
+
}
|
|
1091
|
+
function formatProviderOptions(settings, activeProvider) {
|
|
1092
|
+
const rows = RUNNABLE_CHAT_PROVIDER_IDS.map((provider) => {
|
|
1093
|
+
const status = settings?.statuses[provider];
|
|
1094
|
+
const marker = provider === activeProvider ? "*" : " ";
|
|
1095
|
+
const state = status?.available ? "ready" : status?.credential.connected ? "configured" : status?.routing.localByok ? "needs-key" : "unavailable";
|
|
1096
|
+
return `${marker} ${provider.padEnd(25)} ${providerLabel(settings, provider)} - ${state}`;
|
|
1097
|
+
});
|
|
1098
|
+
return ["Providers:", ...rows].join("\n");
|
|
1099
|
+
}
|
|
1100
|
+
function formatModelOptions(settings, provider, model) {
|
|
1101
|
+
const options = modelOptionsForProvider(settings, provider);
|
|
1102
|
+
const active = activeModelId({ provider, model }, settings);
|
|
1103
|
+
const displayOptions = active && !options.some((option) => option.id === active) ? [{ id: active, label: active, description: "manual" }, ...options] : options;
|
|
1104
|
+
if (displayOptions.length === 0) {
|
|
1105
|
+
return `No cached models for ${providerLabel(settings, provider)}. Configure a default model in Desktop settings.`;
|
|
1106
|
+
}
|
|
1107
|
+
const idWidth = Math.min(48, Math.max(...displayOptions.map((option) => option.id.length)));
|
|
1108
|
+
return displayOptions.map((option) => {
|
|
1109
|
+
const marker = option.id === active ? "*" : " ";
|
|
1110
|
+
return `${marker} ${option.id.padEnd(idWidth)} ${option.label} - ${option.description}`;
|
|
1111
|
+
}).join("\n");
|
|
1112
|
+
}
|
|
1113
|
+
function resolveModelSelection(settings, provider, inputModel) {
|
|
1114
|
+
const normalized = normalizeTerminalModel(inputModel);
|
|
1115
|
+
if (!normalized) {
|
|
1116
|
+
return {
|
|
1117
|
+
changed: true,
|
|
1118
|
+
model: null,
|
|
1119
|
+
message: `Model reset to ${defaultModelId(settings, provider) || "provider default"}`
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
const options = modelOptionsForProvider(settings, provider);
|
|
1123
|
+
const exact = options.find((option) => option.id === normalized);
|
|
1124
|
+
if (exact) return { changed: true, model: exact.id, message: `Model set to ${exact.label}` };
|
|
1125
|
+
const matches = options.filter(
|
|
1126
|
+
(option) => `${option.id} ${option.label}`.toLowerCase().includes(normalized.toLowerCase())
|
|
1127
|
+
);
|
|
1128
|
+
if (matches.length === 1) {
|
|
1129
|
+
return { changed: true, model: matches[0].id, message: `Model set to ${matches[0].label}` };
|
|
1130
|
+
}
|
|
1131
|
+
if (matches.length > 1) {
|
|
1132
|
+
const idWidth = Math.min(48, Math.max(...matches.map((option) => option.id.length)));
|
|
1133
|
+
return {
|
|
1134
|
+
changed: false,
|
|
1135
|
+
model: null,
|
|
1136
|
+
message: [
|
|
1137
|
+
`Model matches for ${normalized}:`,
|
|
1138
|
+
...matches.slice(0, 20).map((option) => `${option.id.padEnd(idWidth)} ${option.label}`),
|
|
1139
|
+
matches.length > 20 ? `... ${matches.length - 20} more` : ""
|
|
1140
|
+
].filter(Boolean).join("\n")
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
return { changed: true, model: normalized, message: `Model set to ${normalized}` };
|
|
1144
|
+
}
|
|
1145
|
+
function formatConnectedApps(payload) {
|
|
1146
|
+
const baseUrl = payload?.account.baseUrl ?? payload?.account.activeProfile?.baseUrl ?? null;
|
|
1147
|
+
const teamId = payload?.preferences.defaultTeamId ?? null;
|
|
1148
|
+
const idWidth = Math.max(...CONNECTED_APP_CATALOG.map((app) => app.id.length));
|
|
1149
|
+
const rows = CONNECTED_APP_CATALOG.map((app) => {
|
|
1150
|
+
const url = buildConnectedAppInstallUrl({ appId: app.id, baseUrl, teamId });
|
|
1151
|
+
return `${app.id.padEnd(idWidth)} ${app.label} - ${app.description}
|
|
1152
|
+
${" ".repeat(idWidth + 2)} ${url}`;
|
|
1153
|
+
});
|
|
1154
|
+
return ["Apps:", ...rows, "", "Use /install <app> to open setup."].join("\n");
|
|
1155
|
+
}
|
|
1156
|
+
function installAppOptions(query) {
|
|
1157
|
+
const normalized = query.trim().toLowerCase();
|
|
1158
|
+
return CONNECTED_APP_CATALOG.filter((app) => {
|
|
1159
|
+
if (!normalized) return true;
|
|
1160
|
+
return [app.id, app.label, app.shortLabel, app.category].some(
|
|
1161
|
+
(value) => value.toLowerCase().includes(normalized)
|
|
1162
|
+
);
|
|
1163
|
+
}).map((app) => ({
|
|
1164
|
+
name: `install:${app.id}`,
|
|
1165
|
+
usage: `/install ${app.id}`,
|
|
1166
|
+
description: `${app.label} - ${app.category}`,
|
|
1167
|
+
submitText: `/install ${app.id}`
|
|
1168
|
+
}));
|
|
1169
|
+
}
|
|
1170
|
+
function installAppQuery(text) {
|
|
1171
|
+
const match = text.match(/^\/install(?:\s+([A-Za-z0-9_-]*))?$/);
|
|
1172
|
+
return match ? match[1] ?? "" : null;
|
|
1173
|
+
}
|
|
1174
|
+
function shortSha(value) {
|
|
1175
|
+
return value ? value.slice(0, 10) : "none";
|
|
1176
|
+
}
|
|
1177
|
+
function formatProfileStatus(profile) {
|
|
1178
|
+
if (profile.mode === "none") return "No active OpenPond profile. Run `openpond init`.";
|
|
1179
|
+
const git = profile.git;
|
|
1180
|
+
const lines = [
|
|
1181
|
+
`Profile: ${profile.activeProfile ?? "default"}`,
|
|
1182
|
+
`State: ${profile.summary.state} - ${profile.summary.message}`,
|
|
1183
|
+
`Repo: ${profile.repoPath}`,
|
|
1184
|
+
`Source: ${profile.sourcePath ?? "missing"}`,
|
|
1185
|
+
git ? `Git: ${git.branch ?? "detached"} ${shortSha(git.head)}${git.dirty ? " dirty" : ""}${git.upstream ? ` -> ${git.upstream}` : ""}` : "Git: not initialized",
|
|
1186
|
+
profile.hosted?.sourceCommitSha ? `Hosted: ${profile.hosted.teamId ?? "team"} ${shortSha(profile.hosted.sourceCommitSha)}` : "Hosted: not pushed",
|
|
1187
|
+
`Catalog: ${profile.catalog.actionCount} actions${profile.catalog.stale ? " stale" : ""}${profile.catalog.error ? ` (${profile.catalog.error})` : ""}`,
|
|
1188
|
+
`Setup gate: ${profile.setupGate.status} (${profile.setupGate.blockingCount} blocking, ${profile.setupGate.optionalMissingCount} optional missing)`,
|
|
1189
|
+
profile.summary.defaultAction ? `Default action: ${profile.summary.defaultAction}` : "Default action: none",
|
|
1190
|
+
profile.lastCheck ? `Last check: ${profile.lastCheck.command} ${profile.lastCheck.status} at ${profile.lastCheck.checkedAt}` : "Last check: none",
|
|
1191
|
+
profile.summary.checkStaleReason ? `Check stale: ${profile.summary.checkStaleReason}` : "",
|
|
1192
|
+
profile.setupGate.blockingRequirements.length ? `Blocking setup: ${profile.setupGate.blockingRequirements.map(formatProfileSetupRequirement).join(", ")}` : "",
|
|
1193
|
+
profile.error ? `Error: ${profile.error}` : ""
|
|
1194
|
+
];
|
|
1195
|
+
return lines.filter(Boolean).join("\n");
|
|
1196
|
+
}
|
|
1197
|
+
function formatProfileDiff(profile) {
|
|
1198
|
+
if (profile.mode === "none") return "No active OpenPond profile. Run `openpond init`.";
|
|
1199
|
+
const diff = profile.diff;
|
|
1200
|
+
const lines = [
|
|
1201
|
+
diff.newAgents.length ? `New agents: ${diff.newAgents.join(", ")}` : "",
|
|
1202
|
+
diff.changedAgents.length ? `Changed agents: ${diff.changedAgents.join(", ")}` : "",
|
|
1203
|
+
diff.deletedAgents.length ? `Deleted agents: ${diff.deletedAgents.join(", ")}` : "",
|
|
1204
|
+
diff.changedSkills.length ? `Changed skills: ${diff.changedSkills.join(", ")}` : "",
|
|
1205
|
+
diff.changedActions.length ? `Changed actions: ${diff.changedActions.join(", ")}` : "",
|
|
1206
|
+
diff.changedExtensions.length ? `Extensions: ${diff.changedExtensions.join(", ")}` : "",
|
|
1207
|
+
diff.setupChanges.length ? `Setup: ${diff.setupChanges.join(", ")}` : "",
|
|
1208
|
+
diff.envRequirementChanges.length ? `Env requirements: ${diff.envRequirementChanges.join(", ")}` : ""
|
|
1209
|
+
].filter(Boolean);
|
|
1210
|
+
if (diff.files.length) {
|
|
1211
|
+
lines.push(
|
|
1212
|
+
"Files:",
|
|
1213
|
+
...diff.files.slice(0, 25).map((file) => `${file.status.padEnd(2)} ${file.path}`)
|
|
1214
|
+
);
|
|
1215
|
+
if (diff.files.length > 25) lines.push(`... ${diff.files.length - 25} more files`);
|
|
1216
|
+
}
|
|
1217
|
+
return lines.length ? lines.join("\n") : "No local profile diff.";
|
|
1218
|
+
}
|
|
1219
|
+
function formatProfileCatalog(profile) {
|
|
1220
|
+
if (profile.mode === "none") return "No active OpenPond profile. Run `openpond init`.";
|
|
1221
|
+
if (profile.actionCatalog.length === 0) return "No profile actions found. Run `openpond profile check build`.";
|
|
1222
|
+
return profile.actionCatalog.map((action) => {
|
|
1223
|
+
const label = action.label ?? action.name ?? action.id;
|
|
1224
|
+
const description = action.description ? ` - ${action.description}` : "";
|
|
1225
|
+
const setup = blockingSetupRequirementsForAction(profile, action.id);
|
|
1226
|
+
const setupStatus = setup.length ? ` setup_required:${setup.map((requirement) => requirement.label).join(",")}` : "";
|
|
1227
|
+
return `${action.id} ${label}${description}${setupStatus}`;
|
|
1228
|
+
}).join("\n");
|
|
1229
|
+
}
|
|
1230
|
+
function formatProfileAgents(profile, activeAgentId) {
|
|
1231
|
+
if (profile.agents.length === 0) return "No profile agents found.";
|
|
1232
|
+
const agentLines = profile.agents.map(
|
|
1233
|
+
(agent) => `${agent.id === activeAgentId ? "*" : " "} ${agent.id} ${agent.enabled ? "enabled" : "disabled"} ${agent.path}`
|
|
1234
|
+
);
|
|
1235
|
+
const actionLines = profile.actionCatalog.map((action) => {
|
|
1236
|
+
const setup = blockingSetupRequirementsForAction(profile, action.id);
|
|
1237
|
+
const setupStatus = setup.length ? ` setup_required:${setup.map((requirement) => requirement.label).join(",")}` : "";
|
|
1238
|
+
return ` /run ${action.id} ${action.label ?? action.name ?? ""}${setupStatus}`.trimEnd();
|
|
1239
|
+
});
|
|
1240
|
+
return [...agentLines, actionLines.length ? "\nActions:" : "", ...actionLines].filter(Boolean).join("\n");
|
|
1241
|
+
}
|
|
1242
|
+
function blockingSetupRequirementsForAction(profile, actionId) {
|
|
1243
|
+
return profile.setupGate.blockingRequirements.filter(
|
|
1244
|
+
(requirement) => requirement.actionId === null || requirement.actionId === actionId
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
function formatProfileSetupRequirement(requirement) {
|
|
1248
|
+
const prefix = requirement.actionId ? `${requirement.actionId}: ` : "";
|
|
1249
|
+
return `${prefix}${requirement.label} (${requirement.status})`;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// ../terminal/src/args.ts
|
|
1253
|
+
var TerminalUsageError = class extends Error {
|
|
1254
|
+
exitCode = 2;
|
|
1255
|
+
constructor(message) {
|
|
1256
|
+
super(message);
|
|
1257
|
+
this.name = "TerminalUsageError";
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1260
|
+
function parseTerminalArgs(argv, defaultCwd = process.cwd()) {
|
|
1261
|
+
const [command = "chat", ...rest] = argv;
|
|
1262
|
+
const options = {
|
|
1263
|
+
server: "http://127.0.0.1:17874",
|
|
1264
|
+
provider: DEFAULT_CHAT_PROVIDER,
|
|
1265
|
+
model: null,
|
|
1266
|
+
cwd: defaultCwd,
|
|
1267
|
+
project: null,
|
|
1268
|
+
resume: null,
|
|
1269
|
+
noServerStart: false,
|
|
1270
|
+
message: null,
|
|
1271
|
+
messageFile: null,
|
|
1272
|
+
stdin: false,
|
|
1273
|
+
nonInteractive: false,
|
|
1274
|
+
json: false,
|
|
1275
|
+
yes: false,
|
|
1276
|
+
approvalPolicy: "on-request",
|
|
1277
|
+
sandbox: "workspace-write",
|
|
1278
|
+
timeoutSec: null,
|
|
1279
|
+
maxOutputBytes: null
|
|
1280
|
+
};
|
|
1281
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
1282
|
+
const arg = rest[i];
|
|
1283
|
+
if (arg === "--server") options.server = rest[++i] ?? options.server;
|
|
1284
|
+
else if (arg === "--provider") {
|
|
1285
|
+
options.provider = normalizeTerminalProvider(rest[++i] ?? null) ?? options.provider;
|
|
1286
|
+
} else if (arg === "--model") {
|
|
1287
|
+
const selection = parseProviderModelSelection(rest[++i] ?? null, options.provider);
|
|
1288
|
+
if (selection.provider) options.provider = selection.provider;
|
|
1289
|
+
options.model = selection.model;
|
|
1290
|
+
} else if (arg === "--cwd") {
|
|
1291
|
+
options.cwd = path.resolve(rest[++i] ?? options.cwd);
|
|
1292
|
+
options.cwdExplicit = true;
|
|
1293
|
+
} else if (arg === "--project") {
|
|
1294
|
+
options.project = rest[++i] ?? null;
|
|
1295
|
+
} else if (arg === "--resume") {
|
|
1296
|
+
options.resume = rest[++i] ?? null;
|
|
1297
|
+
} else if (arg === "--no-server-start") {
|
|
1298
|
+
options.noServerStart = true;
|
|
1299
|
+
} else if (arg === "--message") {
|
|
1300
|
+
options.message = readStringArg(rest, ++i, "message");
|
|
1301
|
+
} else if (arg === "--message-file") {
|
|
1302
|
+
options.messageFile = path.resolve(readStringArg(rest, ++i, "message-file"));
|
|
1303
|
+
} else if (arg === "--stdin") {
|
|
1304
|
+
const parsed = readOptionalBoolean(rest, i);
|
|
1305
|
+
options.stdin = parsed.value;
|
|
1306
|
+
i = parsed.index;
|
|
1307
|
+
} else if (arg === "--non-interactive") {
|
|
1308
|
+
const parsed = readOptionalBoolean(rest, i);
|
|
1309
|
+
options.nonInteractive = parsed.value;
|
|
1310
|
+
i = parsed.index;
|
|
1311
|
+
} else if (arg === "--json") {
|
|
1312
|
+
const parsed = readOptionalBoolean(rest, i);
|
|
1313
|
+
options.json = parsed.value;
|
|
1314
|
+
i = parsed.index;
|
|
1315
|
+
} else if (arg === "--yes") {
|
|
1316
|
+
const parsed = readOptionalBoolean(rest, i);
|
|
1317
|
+
options.yes = parsed.value;
|
|
1318
|
+
i = parsed.index;
|
|
1319
|
+
} else if (arg === "--approval-policy") {
|
|
1320
|
+
options.approvalPolicy = parseApprovalPolicy(readStringArg(rest, ++i, "approval-policy"));
|
|
1321
|
+
} else if (arg === "--sandbox") {
|
|
1322
|
+
options.sandbox = parseSandboxMode(readStringArg(rest, ++i, "sandbox"));
|
|
1323
|
+
} else if (arg === "--timeout-sec") {
|
|
1324
|
+
options.timeoutSec = parsePositiveInteger(readStringArg(rest, ++i, "timeout-sec"), "timeout-sec");
|
|
1325
|
+
} else if (arg === "--max-output-bytes") {
|
|
1326
|
+
options.maxOutputBytes = parsePositiveInteger(readStringArg(rest, ++i, "max-output-bytes"), "max-output-bytes");
|
|
1327
|
+
} else if (arg.startsWith("-")) {
|
|
1328
|
+
throw new TerminalUsageError(`unknown option ${arg}`);
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
return { command, options };
|
|
1332
|
+
}
|
|
1333
|
+
function shouldRunOneShotChat(options) {
|
|
1334
|
+
return Boolean(options.nonInteractive || options.message || options.messageFile || options.stdin);
|
|
1335
|
+
}
|
|
1336
|
+
function resolveTerminalChatMode(options, streams) {
|
|
1337
|
+
if (shouldRunOneShotChat(options)) return "one-shot";
|
|
1338
|
+
if (!streams.inputIsTTY || !streams.outputIsTTY) return "line-mode";
|
|
1339
|
+
return "interactive";
|
|
1340
|
+
}
|
|
1341
|
+
function readStringArg(args, index, label) {
|
|
1342
|
+
const value = args[index];
|
|
1343
|
+
if (!value || value.startsWith("--")) {
|
|
1344
|
+
throw new TerminalUsageError(`${label} requires a value`);
|
|
1345
|
+
}
|
|
1346
|
+
return value;
|
|
1347
|
+
}
|
|
1348
|
+
function readOptionalBoolean(args, index) {
|
|
1349
|
+
const next = args[index + 1];
|
|
1350
|
+
if (next && isBooleanLiteral(next)) {
|
|
1351
|
+
return { value: parseBooleanLiteral(next), index: index + 1 };
|
|
1352
|
+
}
|
|
1353
|
+
return { value: true, index };
|
|
1354
|
+
}
|
|
1355
|
+
function isBooleanLiteral(value) {
|
|
1356
|
+
return /^(true|false|1|0|yes|no)$/i.test(value);
|
|
1357
|
+
}
|
|
1358
|
+
function parseBooleanLiteral(value) {
|
|
1359
|
+
return /^(true|1|yes)$/i.test(value);
|
|
1360
|
+
}
|
|
1361
|
+
function parseApprovalPolicy(value) {
|
|
1362
|
+
if (value === "on-request" || value === "never" || value === "on-failure" || value === "untrusted") {
|
|
1363
|
+
return value;
|
|
1364
|
+
}
|
|
1365
|
+
throw new TerminalUsageError("approval-policy must be on-request, never, on-failure, or untrusted");
|
|
1366
|
+
}
|
|
1367
|
+
function parseSandboxMode(value) {
|
|
1368
|
+
if (value === "read-only" || value === "workspace-write" || value === "danger-full-access") {
|
|
1369
|
+
return value;
|
|
1370
|
+
}
|
|
1371
|
+
throw new TerminalUsageError("sandbox must be read-only, workspace-write, or danger-full-access");
|
|
1372
|
+
}
|
|
1373
|
+
function parsePositiveInteger(value, label) {
|
|
1374
|
+
const parsed = Number(value);
|
|
1375
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
1376
|
+
throw new TerminalUsageError(`${label} must be a positive integer`);
|
|
1377
|
+
}
|
|
1378
|
+
return parsed;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// ../terminal/src/connection.ts
|
|
1382
|
+
import { spawn } from "node:child_process";
|
|
1383
|
+
import { promises as fs } from "node:fs";
|
|
1384
|
+
import os from "node:os";
|
|
1385
|
+
import path2 from "node:path";
|
|
1386
|
+
import { createRequire } from "node:module";
|
|
1387
|
+
import { fileURLToPath } from "node:url";
|
|
1388
|
+
var __dirname = path2.dirname(fileURLToPath(import.meta.url));
|
|
1389
|
+
var serverProcess = null;
|
|
1390
|
+
var stopServerPromise = null;
|
|
1391
|
+
function tokenFilePath() {
|
|
1392
|
+
return path2.join(os.homedir(), ".openpond", "openpond-app", "token");
|
|
1393
|
+
}
|
|
1394
|
+
async function readToken() {
|
|
1395
|
+
try {
|
|
1396
|
+
return (await fs.readFile(tokenFilePath(), "utf8")).trim() || null;
|
|
1397
|
+
} catch {
|
|
1398
|
+
return null;
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
async function apiFetch(server, token, route, init) {
|
|
1402
|
+
const headers = new Headers(init?.headers);
|
|
1403
|
+
headers.set("Content-Type", "application/json");
|
|
1404
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
1405
|
+
const response = await fetch(`${server}${route}`, { ...init, headers });
|
|
1406
|
+
if (!response.ok) {
|
|
1407
|
+
const payload = await response.json().catch(() => ({}));
|
|
1408
|
+
const message = payload && typeof payload === "object" && "error" in payload ? String(payload.error) : response.statusText;
|
|
1409
|
+
throw new Error(message);
|
|
1410
|
+
}
|
|
1411
|
+
return await response.json();
|
|
1412
|
+
}
|
|
1413
|
+
async function health(server) {
|
|
1414
|
+
try {
|
|
1415
|
+
const response = await fetch(`${server}/health`);
|
|
1416
|
+
return response.ok;
|
|
1417
|
+
} catch {
|
|
1418
|
+
return false;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
function repoRoot() {
|
|
1422
|
+
return path2.resolve(__dirname, "../../..");
|
|
1423
|
+
}
|
|
1424
|
+
async function startServer(onLog) {
|
|
1425
|
+
const root = repoRoot();
|
|
1426
|
+
const configuredRunner = process.env.OPENPOND_SERVER_RUNNER;
|
|
1427
|
+
const configuredArgs = parseConfiguredServerArgs(process.env.OPENPOND_SERVER_ARGS);
|
|
1428
|
+
const sourceMode = !configuredRunner && path2.basename(__dirname) === "src";
|
|
1429
|
+
const serverEntry = path2.join(root, "apps", "server", sourceMode ? "src/index.ts" : "dist/index.js");
|
|
1430
|
+
const command = configuredRunner ?? process.env.OPENPOND_NODE_BINARY ?? process.execPath;
|
|
1431
|
+
const args = configuredRunner ? configuredArgs : sourceMode ? [createRequire(import.meta.url).resolve("tsx/cli"), serverEntry] : [serverEntry];
|
|
1432
|
+
const child = spawn(command, args, {
|
|
1433
|
+
cwd: process.env.OPENPOND_SERVER_CWD || root,
|
|
1434
|
+
env: process.env,
|
|
1435
|
+
detached: process.platform !== "win32"
|
|
1436
|
+
});
|
|
1437
|
+
serverProcess = child;
|
|
1438
|
+
return new Promise((resolve, reject) => {
|
|
1439
|
+
let settled = false;
|
|
1440
|
+
const cleanup = () => {
|
|
1441
|
+
clearTimeout(timer);
|
|
1442
|
+
child.stdout.off("data", onStdout);
|
|
1443
|
+
child.stderr.off("data", onStderr);
|
|
1444
|
+
child.off("error", onError);
|
|
1445
|
+
child.off("exit", onExit);
|
|
1446
|
+
};
|
|
1447
|
+
const finish = (url) => {
|
|
1448
|
+
if (settled) return;
|
|
1449
|
+
settled = true;
|
|
1450
|
+
cleanup();
|
|
1451
|
+
resolve(url);
|
|
1452
|
+
};
|
|
1453
|
+
const fail = (error) => {
|
|
1454
|
+
if (settled) return;
|
|
1455
|
+
settled = true;
|
|
1456
|
+
cleanup();
|
|
1457
|
+
reject(error);
|
|
1458
|
+
};
|
|
1459
|
+
const parser = createReadyLineParser("OPENPOND_APP_SERVER_READY ", (payload) => {
|
|
1460
|
+
finish(payload.url || "http://127.0.0.1:17874");
|
|
1461
|
+
});
|
|
1462
|
+
const timer = setTimeout(() => fail(new Error("server start timed out")), 15e3);
|
|
1463
|
+
const onStdout = (chunk) => {
|
|
1464
|
+
try {
|
|
1465
|
+
parser.push(chunk.toString("utf8"));
|
|
1466
|
+
} catch (error) {
|
|
1467
|
+
fail(error instanceof Error ? error : new Error(String(error)));
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
const onStderr = (chunk) => {
|
|
1471
|
+
const text = chunk.toString("utf8").trim();
|
|
1472
|
+
if (text) onLog(text);
|
|
1473
|
+
};
|
|
1474
|
+
const onError = (error) => fail(error);
|
|
1475
|
+
const onExit = (code, signal) => {
|
|
1476
|
+
parser.flush();
|
|
1477
|
+
fail(new Error(`server exited with ${signal ? `signal ${signal}` : `code ${code ?? "unknown"}`}`));
|
|
1478
|
+
};
|
|
1479
|
+
child.stdout.on("data", onStdout);
|
|
1480
|
+
child.stderr.on("data", onStderr);
|
|
1481
|
+
child.once("error", onError);
|
|
1482
|
+
child.once("exit", onExit);
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
function parseConfiguredServerArgs(value) {
|
|
1486
|
+
if (!value) return [];
|
|
1487
|
+
try {
|
|
1488
|
+
const parsed = JSON.parse(value);
|
|
1489
|
+
if (Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) return parsed;
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
throw new Error("OPENPOND_SERVER_ARGS must be a JSON string array");
|
|
1493
|
+
}
|
|
1494
|
+
async function ensureServer(options, onLog) {
|
|
1495
|
+
let server = options.server;
|
|
1496
|
+
if (!await health(server)) {
|
|
1497
|
+
if (options.noServerStart) throw new Error(`No server is listening at ${server}`);
|
|
1498
|
+
try {
|
|
1499
|
+
server = await startServer(onLog);
|
|
1500
|
+
} catch (error) {
|
|
1501
|
+
await stopManagedServer();
|
|
1502
|
+
throw error;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
const token = await readToken();
|
|
1506
|
+
if (!token) throw new Error("Missing OpenPond App capability token");
|
|
1507
|
+
return { server, token };
|
|
1508
|
+
}
|
|
1509
|
+
function stopManagedServer() {
|
|
1510
|
+
if (stopServerPromise) return stopServerPromise;
|
|
1511
|
+
const child = serverProcess;
|
|
1512
|
+
serverProcess = null;
|
|
1513
|
+
if (!child) return Promise.resolve();
|
|
1514
|
+
stopServerPromise = stopTerminalProcessTree(child).finally(() => {
|
|
1515
|
+
stopServerPromise = null;
|
|
1516
|
+
});
|
|
1517
|
+
return stopServerPromise;
|
|
1518
|
+
}
|
|
1519
|
+
async function stopTerminalProcessTree(child, options = {}) {
|
|
1520
|
+
terminateProcessTree(child, "SIGTERM");
|
|
1521
|
+
if (await waitForExit(child, options.gracefulTimeoutMs ?? 5e3)) return;
|
|
1522
|
+
terminateProcessTree(child, "SIGKILL");
|
|
1523
|
+
if (!await waitForExit(child, options.killTimeoutMs ?? 2e3)) {
|
|
1524
|
+
throw new Error(`terminal-owned server process tree ${child.pid ?? "unknown"} did not exit`);
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
function terminateProcessTree(child, signal) {
|
|
1528
|
+
if (!child.pid || child.exitCode !== null) return;
|
|
1529
|
+
if (process.platform === "win32") {
|
|
1530
|
+
const args = ["/pid", String(child.pid), "/t", ...signal === "SIGKILL" ? ["/f"] : []];
|
|
1531
|
+
spawn("taskkill", args, { stdio: "ignore", windowsHide: true }).unref();
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
try {
|
|
1535
|
+
process.kill(-child.pid, signal);
|
|
1536
|
+
} catch {
|
|
1537
|
+
child.kill(signal);
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
async function waitForExit(child, timeoutMs) {
|
|
1541
|
+
if (child.exitCode !== null || child.signalCode !== null) return true;
|
|
1542
|
+
return new Promise((resolve) => {
|
|
1543
|
+
const timer = setTimeout(() => {
|
|
1544
|
+
child.off("exit", onExit);
|
|
1545
|
+
resolve(false);
|
|
1546
|
+
}, timeoutMs);
|
|
1547
|
+
const onExit = () => {
|
|
1548
|
+
clearTimeout(timer);
|
|
1549
|
+
resolve(true);
|
|
1550
|
+
};
|
|
1551
|
+
child.once("exit", onExit);
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
// ../terminal/src/projects.ts
|
|
1556
|
+
function resolveTerminalProjectTarget(payload, query) {
|
|
1557
|
+
const normalized = normalizeProjectQuery(query);
|
|
1558
|
+
if (!normalized) return null;
|
|
1559
|
+
const localProject = payload.localProjects.find((project) => matchesLocalProject(project, normalized));
|
|
1560
|
+
if (localProject) {
|
|
1561
|
+
return {
|
|
1562
|
+
kind: "local_project",
|
|
1563
|
+
id: localProject.id,
|
|
1564
|
+
label: localProject.name,
|
|
1565
|
+
session: {
|
|
1566
|
+
appId: localProject.linkedOpenPondApp?.appId ?? null,
|
|
1567
|
+
appName: localProject.linkedOpenPondApp?.appName ?? null,
|
|
1568
|
+
workspaceKind: "local_project",
|
|
1569
|
+
workspaceId: localProject.id,
|
|
1570
|
+
workspaceName: localProject.name,
|
|
1571
|
+
localProjectId: localProject.id,
|
|
1572
|
+
cloudProjectId: localProject.linkedSandboxProject?.projectId ?? null,
|
|
1573
|
+
cloudTeamId: localProject.linkedSandboxProject?.teamId ?? null,
|
|
1574
|
+
cwd: localProject.workspacePath
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
const cloudProject = payload.cloudProjects.find((project) => matchesCloudProject(project, normalized));
|
|
1579
|
+
if (cloudProject) {
|
|
1580
|
+
return {
|
|
1581
|
+
kind: "cloud_project",
|
|
1582
|
+
id: cloudProject.id,
|
|
1583
|
+
label: cloudProject.name,
|
|
1584
|
+
provider: "openpond",
|
|
1585
|
+
session: {
|
|
1586
|
+
appId: null,
|
|
1587
|
+
appName: null,
|
|
1588
|
+
workspaceKind: "sandbox",
|
|
1589
|
+
workspaceId: cloudProject.id,
|
|
1590
|
+
workspaceName: cloudProject.name,
|
|
1591
|
+
cloudProjectId: cloudProject.id,
|
|
1592
|
+
cloudTeamId: cloudProject.teamId,
|
|
1593
|
+
cwd: null
|
|
1594
|
+
}
|
|
1595
|
+
};
|
|
1596
|
+
}
|
|
1597
|
+
const app = payload.apps.find((candidate) => matchesApp(candidate, normalized));
|
|
1598
|
+
if (app) {
|
|
1599
|
+
return {
|
|
1600
|
+
kind: "sandbox_app",
|
|
1601
|
+
id: app.id,
|
|
1602
|
+
label: app.name,
|
|
1603
|
+
session: {
|
|
1604
|
+
appId: app.id,
|
|
1605
|
+
appName: app.name,
|
|
1606
|
+
workspaceKind: "sandbox_app",
|
|
1607
|
+
workspaceId: app.id,
|
|
1608
|
+
workspaceName: app.name
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
}
|
|
1612
|
+
return null;
|
|
1613
|
+
}
|
|
1614
|
+
function formatTerminalProjects(payload) {
|
|
1615
|
+
const rows = [
|
|
1616
|
+
...payload.localProjects.map((project) => `local ${project.id} ${project.name} ${project.workspacePath}`),
|
|
1617
|
+
...payload.cloudProjects.map((project) => `cloud ${project.id} ${project.name}${project.slug ? ` (${project.slug})` : ""}`),
|
|
1618
|
+
...payload.apps.map((app) => `app ${app.id} ${app.name}`)
|
|
1619
|
+
];
|
|
1620
|
+
return rows.length ? rows.join("\n") : "No local projects, cloud projects, or OpenPond apps returned.";
|
|
1621
|
+
}
|
|
1622
|
+
function normalizeProjectQuery(value) {
|
|
1623
|
+
return value?.trim().toLowerCase() ?? "";
|
|
1624
|
+
}
|
|
1625
|
+
function matchesLocalProject(project, query) {
|
|
1626
|
+
return [project.id, project.name, project.path, project.workspacePath, project.repoPath ?? ""].some(
|
|
1627
|
+
(value) => value.trim().toLowerCase() === query
|
|
1628
|
+
);
|
|
1629
|
+
}
|
|
1630
|
+
function matchesCloudProject(project, query) {
|
|
1631
|
+
return [project.id, project.name, project.slug ?? ""].some((value) => value.trim().toLowerCase() === query);
|
|
1632
|
+
}
|
|
1633
|
+
function matchesApp(app, query) {
|
|
1634
|
+
return [app.id, app.name].some((value) => value.trim().toLowerCase() === query);
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// ../terminal/src/session-state.ts
|
|
1638
|
+
import { realpath } from "node:fs/promises";
|
|
1639
|
+
import path3 from "node:path";
|
|
1640
|
+
function profileLabel(payload) {
|
|
1641
|
+
if (payload.profile.mode !== "local") return "none";
|
|
1642
|
+
const name = payload.profile.activeProfile ?? "local";
|
|
1643
|
+
const state = payload.profile.summary.state === "ready" ? "" : `:${payload.profile.summary.state}`;
|
|
1644
|
+
return `${name}${state}`;
|
|
1645
|
+
}
|
|
1646
|
+
function findTerminalSession(payload, sessionId) {
|
|
1647
|
+
if (!sessionId) return null;
|
|
1648
|
+
return [...payload.sessions, ...payload.codexHistorySessions ?? []].find(
|
|
1649
|
+
(session) => session.id === sessionId
|
|
1650
|
+
) ?? null;
|
|
1651
|
+
}
|
|
1652
|
+
function resolveResumedTerminalSelection(payload, sessionId, selection) {
|
|
1653
|
+
const resumed = findTerminalSession(payload, sessionId);
|
|
1654
|
+
if (resumed?.modelRef) {
|
|
1655
|
+
return {
|
|
1656
|
+
provider: resumed.modelRef.providerId,
|
|
1657
|
+
model: resumed.modelRef.modelId
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
if (resumed?.provider) {
|
|
1661
|
+
return {
|
|
1662
|
+
...selection,
|
|
1663
|
+
provider: resumed.provider
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
return selection;
|
|
1667
|
+
}
|
|
1668
|
+
async function createTerminalChatSession(connection, payload, options) {
|
|
1669
|
+
const target = resolveTerminalProjectTarget(payload, options.project);
|
|
1670
|
+
if (target && options.cwdExplicit) {
|
|
1671
|
+
if (target.kind !== "local_project" || !target.session.cwd) {
|
|
1672
|
+
throw new Error("--cwd can only be combined with a local --project target.");
|
|
1673
|
+
}
|
|
1674
|
+
await assertLocalProjectCwd(target.session.cwd, options.cwd);
|
|
1675
|
+
}
|
|
1676
|
+
const provider = target?.provider ?? options.provider;
|
|
1677
|
+
const sessionOptions = { ...options, provider };
|
|
1678
|
+
const targetSession = target ? {
|
|
1679
|
+
...target.session,
|
|
1680
|
+
...options.cwdExplicit ? { cwd: options.cwd } : {}
|
|
1681
|
+
} : {
|
|
1682
|
+
appId: options.project,
|
|
1683
|
+
appName: null,
|
|
1684
|
+
cwd: options.cwd
|
|
1685
|
+
};
|
|
1686
|
+
const metadata = {
|
|
1687
|
+
...targetSession.metadata ?? {},
|
|
1688
|
+
...options.headless ? {
|
|
1689
|
+
openpondTerminalMode: "one-shot",
|
|
1690
|
+
openpondTerminal: {
|
|
1691
|
+
mode: "one-shot",
|
|
1692
|
+
sandbox: options.sandbox ?? null
|
|
1693
|
+
}
|
|
1694
|
+
} : {}
|
|
1695
|
+
};
|
|
1696
|
+
const body = {
|
|
1697
|
+
provider,
|
|
1698
|
+
modelRef: activeModelRef(sessionOptions, payload.providers),
|
|
1699
|
+
title: target?.label ? `${target.label} terminal` : "Terminal chat",
|
|
1700
|
+
...targetSession,
|
|
1701
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {},
|
|
1702
|
+
...options.headless && (options.yes || options.approvalPolicy === "never") ? { openPondCommandAccessMode: "full-access" } : {}
|
|
1703
|
+
};
|
|
1704
|
+
if (typeof targetSession.cwd === "string" && targetSession.cwd) {
|
|
1705
|
+
options.cwd = targetSession.cwd;
|
|
1706
|
+
}
|
|
1707
|
+
return apiFetch(connection.server, connection.token, "/v1/sessions", {
|
|
1708
|
+
method: "POST",
|
|
1709
|
+
body: JSON.stringify(body),
|
|
1710
|
+
signal: connection.signal
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
async function ensureTerminalChatSession(connection, payload, options, requestedSessionId) {
|
|
1714
|
+
const selection = resolveResumedTerminalSelection(payload, requestedSessionId, {
|
|
1715
|
+
provider: options.provider,
|
|
1716
|
+
model: options.model
|
|
1717
|
+
});
|
|
1718
|
+
if (requestedSessionId) {
|
|
1719
|
+
if (options.cwdExplicit) {
|
|
1720
|
+
throw new Error("--cwd cannot be combined with --resume; the resumed session workspace is authoritative.");
|
|
1721
|
+
}
|
|
1722
|
+
const resumed = findTerminalSession(payload, requestedSessionId);
|
|
1723
|
+
if (resumed?.cwd) options.cwd = resumed.cwd;
|
|
1724
|
+
return {
|
|
1725
|
+
sessionId: requestedSessionId,
|
|
1726
|
+
session: resumed,
|
|
1727
|
+
...selection
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
const session = await createTerminalChatSession(connection, payload, {
|
|
1731
|
+
...options,
|
|
1732
|
+
...selection
|
|
1733
|
+
});
|
|
1734
|
+
return {
|
|
1735
|
+
sessionId: session.id,
|
|
1736
|
+
session,
|
|
1737
|
+
...selection
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1740
|
+
async function ensureTerminalSessionWorkspaceReady(connection, session) {
|
|
1741
|
+
if (!session || session.workspaceKind !== "sandbox" && session.workspaceKind !== "sandbox_template") {
|
|
1742
|
+
return session;
|
|
1743
|
+
}
|
|
1744
|
+
const result = await apiFetch(
|
|
1745
|
+
connection.server,
|
|
1746
|
+
connection.token,
|
|
1747
|
+
`/v1/sessions/${encodeURIComponent(session.id)}/workspace/ensure-ready`,
|
|
1748
|
+
{
|
|
1749
|
+
method: "POST",
|
|
1750
|
+
body: JSON.stringify({ surface: "terminal" }),
|
|
1751
|
+
signal: connection.signal
|
|
1752
|
+
}
|
|
1753
|
+
);
|
|
1754
|
+
return result.session;
|
|
1755
|
+
}
|
|
1756
|
+
async function assertLocalProjectCwd(projectCwd, requestedCwd) {
|
|
1757
|
+
const [projectRoot, requested] = await Promise.all([realpath(projectCwd), realpath(requestedCwd)]);
|
|
1758
|
+
const relation = path3.relative(projectRoot, requested);
|
|
1759
|
+
if (relation.startsWith("..") || path3.isAbsolute(relation)) {
|
|
1760
|
+
throw new Error(`--cwd must be the selected local project or one of its subdirectories: ${projectRoot}`);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// ../terminal/src/permissions.ts
|
|
1765
|
+
var TERMINAL_PERMISSION_QUESTION_CHOICES = [
|
|
1766
|
+
{ name: "yes", usage: "yes", description: "approve this command", submitText: "yes" },
|
|
1767
|
+
{ name: "session", usage: "session", description: "approve this command family", submitText: "session" },
|
|
1768
|
+
{ name: "no", usage: "no", description: "deny this command", submitText: "no" },
|
|
1769
|
+
{ name: "skip", usage: "skip", description: "cancel this command request", submitText: "skip" }
|
|
1770
|
+
];
|
|
1771
|
+
function parseTerminalPermissionChoice(value) {
|
|
1772
|
+
const normalized = value?.trim().toLowerCase();
|
|
1773
|
+
if (normalized === "yes" || normalized === "y" || normalized === "approve") return "yes";
|
|
1774
|
+
if (normalized === "session" || normalized === "s") return "session";
|
|
1775
|
+
if (normalized === "no" || normalized === "n" || normalized === "deny") return "no";
|
|
1776
|
+
if (normalized === "skip" || normalized === "cancel") return "skip";
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
function terminalPermissionDecision(choice) {
|
|
1780
|
+
if (choice === "yes") return "accept";
|
|
1781
|
+
if (choice === "session") return "acceptForSession";
|
|
1782
|
+
if (choice === "skip") return "cancel";
|
|
1783
|
+
return "decline";
|
|
1784
|
+
}
|
|
1785
|
+
function parseTerminalPermissionMode(value) {
|
|
1786
|
+
const normalized = value?.trim().toLowerCase();
|
|
1787
|
+
if (!normalized) return null;
|
|
1788
|
+
if (normalized === "full" || normalized === "full-access" || normalized === "full_access") {
|
|
1789
|
+
return "full-access";
|
|
1790
|
+
}
|
|
1791
|
+
const parsed = OpenPondCommandAccessModeSchema.safeParse(normalized);
|
|
1792
|
+
return parsed.success ? parsed.data : null;
|
|
1793
|
+
}
|
|
1794
|
+
function formatTerminalPermissionMode(mode) {
|
|
1795
|
+
if (mode === "full-access") return "Full access";
|
|
1796
|
+
if (mode === "disabled") return "Disabled";
|
|
1797
|
+
return "Ask";
|
|
1798
|
+
}
|
|
1799
|
+
function latestPendingCommandApproval(payload, sessionId) {
|
|
1800
|
+
if (!payload || !sessionId) return null;
|
|
1801
|
+
return (payload.approvals ?? []).filter(
|
|
1802
|
+
(approval) => approval.kind === "command" && approval.status === "pending" && approval.sessionId === sessionId
|
|
1803
|
+
).sort((left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt))[0] ?? null;
|
|
1804
|
+
}
|
|
1805
|
+
function commandApprovalFromRuntimeEvent(event) {
|
|
1806
|
+
if (event.name !== "approval.requested") return null;
|
|
1807
|
+
const parsed = ApprovalSchema.safeParse(event.data);
|
|
1808
|
+
if (!parsed.success || parsed.data.kind !== "command" || parsed.data.status !== "pending") return null;
|
|
1809
|
+
return parsed.data;
|
|
1810
|
+
}
|
|
1811
|
+
function commandApprovalIdFromResolvedEvent(event) {
|
|
1812
|
+
if (event.name !== "approval.resolved") return null;
|
|
1813
|
+
const data = event.data && typeof event.data === "object" && !Array.isArray(event.data) ? event.data : null;
|
|
1814
|
+
const value = data?.approvalId ?? data?.approval_id ?? data?.id;
|
|
1815
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1816
|
+
}
|
|
1817
|
+
function commandAccessModeForSession(payload, sessionId) {
|
|
1818
|
+
const session = payload?.sessions.find((candidate) => candidate.id === sessionId) ?? payload?.codexHistorySessions?.find((candidate) => candidate.id === sessionId) ?? null;
|
|
1819
|
+
return session?.openPondCommandAccessMode ?? payload?.preferences?.openPondCommandAccessMode ?? DEFAULT_OPENPOND_COMMAND_ACCESS_MODE;
|
|
1820
|
+
}
|
|
1821
|
+
function formatTerminalPermissionsSummary(mode) {
|
|
1822
|
+
return [
|
|
1823
|
+
`Command access: ${formatTerminalPermissionMode(mode)}`,
|
|
1824
|
+
"Use /permissions ask or /permissions full-access to update this chat.",
|
|
1825
|
+
"When a command approval is pending, /permissions opens yes/session/no/skip choices."
|
|
1826
|
+
].join("\n");
|
|
1827
|
+
}
|
|
1828
|
+
function formatTerminalCommandApprovalQuestion(approval) {
|
|
1829
|
+
const detail = commandApprovalDetail(approval);
|
|
1830
|
+
const rows = [
|
|
1831
|
+
"Command approval",
|
|
1832
|
+
`command: ${detail.command ?? approval.title}`,
|
|
1833
|
+
detail.cwd ? `cwd: ${detail.cwd}` : null,
|
|
1834
|
+
detail.risk ? `risk: ${detail.risk}` : null,
|
|
1835
|
+
detail.timeoutSeconds ? `timeout: ${detail.timeoutSeconds}s` : null,
|
|
1836
|
+
detail.familyLabel ? `session approval family: ${detail.familyLabel}` : null,
|
|
1837
|
+
"Choose: yes, session, no, or skip."
|
|
1838
|
+
];
|
|
1839
|
+
return rows.filter(Boolean).join("\n");
|
|
1840
|
+
}
|
|
1841
|
+
function commandApprovalDetail(approval) {
|
|
1842
|
+
try {
|
|
1843
|
+
const parsed = JSON.parse(approval.detail);
|
|
1844
|
+
const family = parsed.sessionApprovalFamily && typeof parsed.sessionApprovalFamily === "object" && !Array.isArray(parsed.sessionApprovalFamily) ? parsed.sessionApprovalFamily : null;
|
|
1845
|
+
return {
|
|
1846
|
+
command: stringValue2(parsed.command),
|
|
1847
|
+
cwd: stringValue2(parsed.cwd),
|
|
1848
|
+
risk: stringValue2(parsed.risk),
|
|
1849
|
+
timeoutSeconds: typeof parsed.timeoutSeconds === "number" ? parsed.timeoutSeconds : null,
|
|
1850
|
+
familyLabel: stringValue2(family?.label)
|
|
1851
|
+
};
|
|
1852
|
+
} catch {
|
|
1853
|
+
return {
|
|
1854
|
+
command: null,
|
|
1855
|
+
cwd: null,
|
|
1856
|
+
risk: null,
|
|
1857
|
+
timeoutSeconds: null,
|
|
1858
|
+
familyLabel: null
|
|
1859
|
+
};
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
function stringValue2(value) {
|
|
1863
|
+
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
// ../terminal/src/command-handlers.ts
|
|
1867
|
+
async function handleTerminalSlashCommand(command, context) {
|
|
1868
|
+
const connection = context.getConnection();
|
|
1869
|
+
if (!connection) return;
|
|
1870
|
+
if (command.type === "exit") {
|
|
1871
|
+
context.requestExit();
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1874
|
+
if (command.type === "clear") {
|
|
1875
|
+
context.clearTranscript();
|
|
1876
|
+
context.render();
|
|
1877
|
+
return;
|
|
1878
|
+
}
|
|
1879
|
+
if (command.type === "help") {
|
|
1880
|
+
context.addItem(systemItem(helpText()));
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
if (command.type === "projects") {
|
|
1884
|
+
const latest = await context.refreshBootstrap();
|
|
1885
|
+
context.addItem(systemItem(formatTerminalProjects(latest)));
|
|
1886
|
+
return;
|
|
1887
|
+
}
|
|
1888
|
+
if (command.type === "providers") {
|
|
1889
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1890
|
+
context.addItem(systemItem(formatProviderOptions(latest.providers, context.options.provider)));
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
if (command.type === "provider") {
|
|
1894
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1895
|
+
if (!command.id) {
|
|
1896
|
+
context.addItem(systemItem(formatProviderOptions(latest.providers, context.options.provider)));
|
|
1897
|
+
return;
|
|
1898
|
+
}
|
|
1899
|
+
const provider = normalizeTerminalProvider(command.id);
|
|
1900
|
+
if (!provider) {
|
|
1901
|
+
context.addItem(systemItem(`Unknown provider: ${command.id}
|
|
1902
|
+
|
|
1903
|
+
${formatProviderOptions(latest.providers, context.options.provider)}`, "warning"));
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
context.options.provider = provider;
|
|
1907
|
+
context.options.model = null;
|
|
1908
|
+
context.addItem(systemItem(`Provider set to ${providerLabel(latest.providers, provider)} / ${modelLabel(latest.providers, context.options)}`));
|
|
1909
|
+
context.render();
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
if (command.type === "apps") {
|
|
1913
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1914
|
+
context.addItem(systemItem(formatConnectedApps(latest)));
|
|
1915
|
+
return;
|
|
1916
|
+
}
|
|
1917
|
+
if (command.type === "install") {
|
|
1918
|
+
const app = connectedAppById(command.id);
|
|
1919
|
+
if (!app) {
|
|
1920
|
+
context.addItem(systemItem(`Unknown app: ${command.id}
|
|
1921
|
+
|
|
1922
|
+
${formatConnectedApps(context.getPayload())}`, "warning"));
|
|
1923
|
+
return;
|
|
1924
|
+
}
|
|
1925
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1926
|
+
const url = buildConnectedAppInstallUrl({
|
|
1927
|
+
appId: app.id,
|
|
1928
|
+
baseUrl: latest.account.baseUrl ?? latest.account.activeProfile?.baseUrl ?? null,
|
|
1929
|
+
teamId: latest.preferences.defaultTeamId ?? null
|
|
1930
|
+
});
|
|
1931
|
+
context.openUrl(url);
|
|
1932
|
+
context.addItem(systemItem(`Opened ${app.label} setup:
|
|
1933
|
+
${url}`));
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
if (command.type === "project") {
|
|
1937
|
+
if (!command.id) {
|
|
1938
|
+
context.addItem(systemItem("Usage: /project <id>", "warning"));
|
|
1939
|
+
return;
|
|
1940
|
+
}
|
|
1941
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1942
|
+
const target = resolveTerminalProjectTarget(latest, command.id);
|
|
1943
|
+
if (!target) {
|
|
1944
|
+
context.addItem(systemItem(`Project not found: ${command.id}
|
|
1945
|
+
|
|
1946
|
+
${formatTerminalProjects(latest)}`, "warning"));
|
|
1947
|
+
return;
|
|
1948
|
+
}
|
|
1949
|
+
const nextOptions = {
|
|
1950
|
+
...context.options,
|
|
1951
|
+
project: target.id,
|
|
1952
|
+
provider: target.provider ?? context.options.provider,
|
|
1953
|
+
model: target.provider ? null : context.options.model
|
|
1954
|
+
};
|
|
1955
|
+
await createAndActivateSession(
|
|
1956
|
+
context,
|
|
1957
|
+
() => createTerminalChatSession(connection, latest, nextOptions)
|
|
1958
|
+
);
|
|
1959
|
+
context.options.project = nextOptions.project;
|
|
1960
|
+
context.options.provider = nextOptions.provider;
|
|
1961
|
+
context.options.model = nextOptions.model;
|
|
1962
|
+
context.options.cwd = nextOptions.cwd;
|
|
1963
|
+
context.addItem(systemItem(`Project set to ${target.kind}: ${target.label}`));
|
|
1964
|
+
context.render();
|
|
1965
|
+
return;
|
|
1966
|
+
}
|
|
1967
|
+
if (command.type === "model") {
|
|
1968
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
1969
|
+
if (!command.id) {
|
|
1970
|
+
context.addItem(systemItem(formatModelOptions(latest.providers, context.options.provider, context.options.model)));
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
const selection = parseProviderModelSelection(command.id, context.options.provider);
|
|
1974
|
+
if (selection.provider) {
|
|
1975
|
+
context.options.provider = selection.provider;
|
|
1976
|
+
}
|
|
1977
|
+
const resolved = resolveModelSelection(
|
|
1978
|
+
latest.providers,
|
|
1979
|
+
context.options.provider,
|
|
1980
|
+
selection.model ?? "default"
|
|
1981
|
+
);
|
|
1982
|
+
if (resolved.changed) context.options.model = resolved.model;
|
|
1983
|
+
context.addItem(systemItem(`${providerLabel(latest.providers, context.options.provider)} / ${resolved.message}`));
|
|
1984
|
+
context.render();
|
|
1985
|
+
return;
|
|
1986
|
+
}
|
|
1987
|
+
if (command.type === "permissions") {
|
|
1988
|
+
await handlePermissionsCommand(command.args, context, connection);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (command.type === "agents") {
|
|
1992
|
+
const latest = await context.refreshBootstrap();
|
|
1993
|
+
context.addItem(systemItem(formatProfileAgents(latest.profile, context.getActiveAgentId())));
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
if (command.type === "agent") {
|
|
1997
|
+
const latest = await context.refreshBootstrap();
|
|
1998
|
+
const agent = latest.profile.agents.find((candidate) => candidate.id === command.id);
|
|
1999
|
+
if (!agent) {
|
|
2000
|
+
context.addItem(systemItem(`Profile agent not found: ${command.id}`, "warning"));
|
|
2001
|
+
return;
|
|
2002
|
+
}
|
|
2003
|
+
context.setActiveAgentId(agent.id);
|
|
2004
|
+
context.addItem(systemItem(`Agent set to ${agent.id}`));
|
|
2005
|
+
context.render();
|
|
2006
|
+
return;
|
|
2007
|
+
}
|
|
2008
|
+
if (command.type === "profile") {
|
|
2009
|
+
await handleProfileCommand(command.args, context, connection);
|
|
2010
|
+
return;
|
|
2011
|
+
}
|
|
2012
|
+
if (command.type === "run") {
|
|
2013
|
+
if (!command.action) {
|
|
2014
|
+
context.addItem(systemItem("Usage: /run <action> [json]", "warning"));
|
|
2015
|
+
return;
|
|
2016
|
+
}
|
|
2017
|
+
const latest = await context.refreshBootstrap();
|
|
2018
|
+
const catalogAction = latest.profile.actionCatalog.find((action) => action.id === command.action);
|
|
2019
|
+
if (!catalogAction) {
|
|
2020
|
+
context.addItem(systemItem(`Profile action not found: ${command.action}. Run /profile catalog.`, "warning"));
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
const blockingSetup = blockingSetupRequirementsForAction(latest.profile, command.action);
|
|
2024
|
+
if (blockingSetup.length > 0) {
|
|
2025
|
+
context.addItem(
|
|
2026
|
+
systemItem(
|
|
2027
|
+
[
|
|
2028
|
+
"agent_source_setup_required",
|
|
2029
|
+
`Profile action ${command.action} has unresolved required setup.`,
|
|
2030
|
+
`Missing setup: ${blockingSetup.map((requirement) => requirement.label).join(", ")}`
|
|
2031
|
+
].join("\n"),
|
|
2032
|
+
"warning"
|
|
2033
|
+
)
|
|
2034
|
+
);
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const result = await apiFetch(connection.server, connection.token, "/v1/profile/run", {
|
|
2038
|
+
method: "POST",
|
|
2039
|
+
body: JSON.stringify({ action: command.action, input: command.input })
|
|
2040
|
+
});
|
|
2041
|
+
context.addItem(systemItem([result.stdout.trim(), result.stderr.trim()].filter(Boolean).join("\n") || `${result.action} completed`));
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
if (command.type === "settings") {
|
|
2045
|
+
await handleSettingsCommand(command.args, context, connection);
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
if (command.type === "logs") {
|
|
2049
|
+
const latest = await context.refreshBootstrap();
|
|
2050
|
+
const lines = latest.events.slice(-20).map((event) => `${event.timestamp} ${event.name} ${event.output ?? event.error ?? ""}`.trim());
|
|
2051
|
+
context.addItem(systemItem(lines.join("\n") || "No recent events."));
|
|
2052
|
+
return;
|
|
2053
|
+
}
|
|
2054
|
+
if (command.type === "compact") {
|
|
2055
|
+
const activeSessionId = context.getActiveSessionId();
|
|
2056
|
+
if (!activeSessionId) return;
|
|
2057
|
+
await apiFetch(connection.server, connection.token, `/v1/sessions/${activeSessionId}/compact`, {
|
|
2058
|
+
method: "POST",
|
|
2059
|
+
body: JSON.stringify({
|
|
2060
|
+
reason: "manual",
|
|
2061
|
+
model: activeModelId(context.options, context.getPayload()?.providers),
|
|
2062
|
+
modelRef: activeModelRef(context.options, context.getPayload()?.providers)
|
|
2063
|
+
})
|
|
2064
|
+
});
|
|
2065
|
+
context.addItem(systemItem("Compaction started."));
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
if (command.type === "hooks") {
|
|
2069
|
+
context.addItem(systemItem("Hooks are registered in the shared local runtime; destructive hooks are disabled by default."));
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
if (command.type === "start") {
|
|
2073
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
2074
|
+
const session = await createAndActivateSession(
|
|
2075
|
+
context,
|
|
2076
|
+
() => createTerminalChatSession(connection, latest, context.options)
|
|
2077
|
+
);
|
|
2078
|
+
context.addItem(systemItem(`Started ${session.id}`));
|
|
2079
|
+
context.render();
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
context.addItem(systemItem(`Unknown command: ${command.command}`, "warning"));
|
|
2083
|
+
}
|
|
2084
|
+
async function createAndActivateSession(context, create) {
|
|
2085
|
+
if (context.switchSession) return context.switchSession(create);
|
|
2086
|
+
const session = await create();
|
|
2087
|
+
context.setActiveSessionId(session.id);
|
|
2088
|
+
return session;
|
|
2089
|
+
}
|
|
2090
|
+
async function handlePermissionsCommand(args, context, connection) {
|
|
2091
|
+
const latest = await context.refreshBootstrap();
|
|
2092
|
+
const activeSessionId = context.getActiveSessionId();
|
|
2093
|
+
const pendingApproval = context.getPendingCommandApproval?.() ?? latestPendingCommandApproval(latest, activeSessionId);
|
|
2094
|
+
const first = args[0] ?? null;
|
|
2095
|
+
const choice = parseTerminalPermissionChoice(first);
|
|
2096
|
+
if (choice) {
|
|
2097
|
+
if (!pendingApproval) {
|
|
2098
|
+
context.addItem(systemItem("No command approval is pending.", "warning"));
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
await resolveTerminalCommandApproval(connection, pendingApproval.id, choice);
|
|
2102
|
+
context.addItem(systemItem(`Command approval resolved: ${choice}.`));
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
if (pendingApproval && !first) {
|
|
2106
|
+
if (context.openCommandApprovalQuestion) {
|
|
2107
|
+
context.openCommandApprovalQuestion(pendingApproval);
|
|
2108
|
+
} else {
|
|
2109
|
+
context.addItem(systemItem(formatTerminalCommandApprovalQuestion(pendingApproval), "warning"));
|
|
2110
|
+
}
|
|
2111
|
+
return;
|
|
2112
|
+
}
|
|
2113
|
+
const mode = parseTerminalPermissionMode(first);
|
|
2114
|
+
if (mode) {
|
|
2115
|
+
if (!activeSessionId) {
|
|
2116
|
+
context.addItem(systemItem("No active session.", "warning"));
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
await patchTerminalSessionCommandAccess(connection, activeSessionId, mode);
|
|
2120
|
+
await context.refreshBootstrap();
|
|
2121
|
+
context.addItem(systemItem(`Command access set to ${formatTerminalPermissionMode(mode)}.`));
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
if (first) {
|
|
2125
|
+
context.addItem(
|
|
2126
|
+
systemItem("Usage: /permissions [ask|full-access|yes|session|no|skip]", "warning")
|
|
2127
|
+
);
|
|
2128
|
+
return;
|
|
2129
|
+
}
|
|
2130
|
+
context.addItem(systemItem(formatTerminalPermissionsSummary(commandAccessModeForSession(latest, activeSessionId))));
|
|
2131
|
+
}
|
|
2132
|
+
async function resolveTerminalCommandApproval(connection, approvalId, choice) {
|
|
2133
|
+
return apiFetch(
|
|
2134
|
+
connection.server,
|
|
2135
|
+
connection.token,
|
|
2136
|
+
`/v1/approvals/${encodeURIComponent(approvalId)}`,
|
|
2137
|
+
{
|
|
2138
|
+
method: "POST",
|
|
2139
|
+
body: JSON.stringify({ decision: terminalPermissionDecision(choice) })
|
|
2140
|
+
}
|
|
2141
|
+
);
|
|
2142
|
+
}
|
|
2143
|
+
async function patchTerminalSessionCommandAccess(connection, sessionId, mode) {
|
|
2144
|
+
await apiFetch(connection.server, connection.token, `/v1/sessions/${encodeURIComponent(sessionId)}`, {
|
|
2145
|
+
method: "PATCH",
|
|
2146
|
+
body: JSON.stringify({ openPondCommandAccessMode: mode })
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
async function handleSettingsCommand(args, context, connection) {
|
|
2150
|
+
const latest = context.getPayload() ?? await context.refreshBootstrap();
|
|
2151
|
+
const subcommand = args[0] ?? "show";
|
|
2152
|
+
if (subcommand === "show") {
|
|
2153
|
+
context.addItem(systemItem(formatTerminalSettings(latest)));
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
if (subcommand !== "goal-storage") {
|
|
2157
|
+
context.addItem(systemItem("Usage: /settings [goal-storage global|workspace]", "warning"));
|
|
2158
|
+
return;
|
|
2159
|
+
}
|
|
2160
|
+
const location = args[1];
|
|
2161
|
+
if (location !== "global" && location !== "workspace") {
|
|
2162
|
+
context.addItem(systemItem("Usage: /settings goal-storage global|workspace", "warning"));
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2165
|
+
const updated = await apiFetch(connection.server, connection.token, "/v1/preferences", {
|
|
2166
|
+
method: "PATCH",
|
|
2167
|
+
body: JSON.stringify({ goalStorageLocation: location })
|
|
2168
|
+
});
|
|
2169
|
+
context.setPayload(updated);
|
|
2170
|
+
context.addItem(systemItem(`Goal storage set to ${goalStorageLabel(updated.preferences.goalStorageLocation)}.`));
|
|
2171
|
+
}
|
|
2172
|
+
function formatTerminalSettings(payload) {
|
|
2173
|
+
return [
|
|
2174
|
+
"Settings:",
|
|
2175
|
+
`Goal storage: ${goalStorageLabel(payload.preferences.goalStorageLocation)}`,
|
|
2176
|
+
"",
|
|
2177
|
+
"Change it with /settings goal-storage global or /settings goal-storage workspace."
|
|
2178
|
+
].join("\n");
|
|
2179
|
+
}
|
|
2180
|
+
function goalStorageLabel(location) {
|
|
2181
|
+
return location === "workspace" ? ".openpond/goals in the working directory" : "~/.openpond/goals";
|
|
2182
|
+
}
|
|
2183
|
+
async function handleProfileCommand(args, context, connection) {
|
|
2184
|
+
const subcommand = args[0] ?? "current";
|
|
2185
|
+
if (subcommand === "init") {
|
|
2186
|
+
const profileState = await apiFetch(connection.server, connection.token, "/v1/profile/init", {
|
|
2187
|
+
method: "POST",
|
|
2188
|
+
body: JSON.stringify({ path: args[1] })
|
|
2189
|
+
});
|
|
2190
|
+
await context.refreshBootstrap();
|
|
2191
|
+
context.addItem(systemItem(JSON.stringify(profileState, null, 2)));
|
|
2192
|
+
return;
|
|
2193
|
+
}
|
|
2194
|
+
if (subcommand === "load") {
|
|
2195
|
+
if (!args[1]) {
|
|
2196
|
+
context.addItem(systemItem("Usage: /profile load <path> [profile]", "warning"));
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const profileState = await apiFetch(connection.server, connection.token, "/v1/profile/load", {
|
|
2200
|
+
method: "POST",
|
|
2201
|
+
body: JSON.stringify({ path: args[1], profile: args[2] })
|
|
2202
|
+
});
|
|
2203
|
+
await context.refreshBootstrap();
|
|
2204
|
+
context.addItem(systemItem(JSON.stringify(profileState, null, 2)));
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
if (subcommand === "check") {
|
|
2208
|
+
const profileState = await apiFetch(connection.server, connection.token, "/v1/profile/check", {
|
|
2209
|
+
method: "POST",
|
|
2210
|
+
body: JSON.stringify({ kind: args[1] ?? "all" })
|
|
2211
|
+
});
|
|
2212
|
+
await context.refreshBootstrap();
|
|
2213
|
+
context.addItem(systemItem(formatProfileStatus(profileState)));
|
|
2214
|
+
return;
|
|
2215
|
+
}
|
|
2216
|
+
if (subcommand === "commit") {
|
|
2217
|
+
const message = args.slice(1).join(" ").trim() || void 0;
|
|
2218
|
+
const result = await apiFetch(connection.server, connection.token, "/v1/profile/commit", {
|
|
2219
|
+
method: "POST",
|
|
2220
|
+
body: JSON.stringify({ message })
|
|
2221
|
+
});
|
|
2222
|
+
await context.refreshBootstrap();
|
|
2223
|
+
context.addItem(systemItem(JSON.stringify(result, null, 2)));
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (subcommand === "push") {
|
|
2227
|
+
const teamId = args[1];
|
|
2228
|
+
if (!teamId) {
|
|
2229
|
+
context.addItem(systemItem("Usage: /profile push <teamId>", "warning"));
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
const result = await apiFetch(connection.server, connection.token, "/v1/profile/push", {
|
|
2233
|
+
method: "POST",
|
|
2234
|
+
body: JSON.stringify({ teamId, ensureHosted: true })
|
|
2235
|
+
});
|
|
2236
|
+
const latest2 = await context.refreshBootstrap();
|
|
2237
|
+
context.addItem(systemItem([`Pushed profile to ${teamId}.`, formatProfileStatus(latest2.profile), JSON.stringify(result, null, 2)].join("\n\n")));
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
const latest = await context.refreshBootstrap();
|
|
2241
|
+
if (latest.profile.mode === "none") {
|
|
2242
|
+
context.addItem(systemItem("No active OpenPond profile. Run `openpond init`.", "warning"));
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
if (subcommand === "diff") {
|
|
2246
|
+
context.addItem(systemItem(formatProfileDiff(latest.profile)));
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
if (subcommand === "catalog" || subcommand === "actions") {
|
|
2250
|
+
context.addItem(systemItem(formatProfileCatalog(latest.profile)));
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
context.addItem(systemItem(formatProfileStatus(latest.profile)));
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
// ../terminal/src/direct-command.ts
|
|
2257
|
+
var TERMINAL_SELECT_PROJECT_MESSAGE = "Select a project to use this.";
|
|
2258
|
+
function isTerminalDirectCommandSession(session) {
|
|
2259
|
+
if (!session || session.provider === "codex") return false;
|
|
2260
|
+
if (session.workspaceKind === "local_project") return Boolean(session.cwd?.trim());
|
|
2261
|
+
if (session.workspaceKind === "sandbox" || session.workspaceKind === "sandbox_template" || session.workspaceKind === "sandbox_app") {
|
|
2262
|
+
return Boolean(session.workspaceId);
|
|
2263
|
+
}
|
|
2264
|
+
return false;
|
|
2265
|
+
}
|
|
2266
|
+
function terminalDirectCommandBlockedReason(session) {
|
|
2267
|
+
return isTerminalDirectCommandSession(session) ? null : TERMINAL_SELECT_PROJECT_MESSAGE;
|
|
2268
|
+
}
|
|
2269
|
+
async function runTerminalDirectCommand(connection, session, command) {
|
|
2270
|
+
return apiFetch(
|
|
2271
|
+
connection.server,
|
|
2272
|
+
connection.token,
|
|
2273
|
+
`/v1/sessions/${encodeURIComponent(session.id)}/commands`,
|
|
2274
|
+
{
|
|
2275
|
+
method: "POST",
|
|
2276
|
+
body: JSON.stringify({
|
|
2277
|
+
command,
|
|
2278
|
+
cwd: session.cwd
|
|
2279
|
+
})
|
|
2280
|
+
}
|
|
2281
|
+
);
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
// ../terminal/src/events.ts
|
|
2285
|
+
function terminalEventStreamRequest(server, token, signal, options = {}) {
|
|
2286
|
+
const headers = new Headers();
|
|
2287
|
+
headers.set("Accept", "text/event-stream");
|
|
2288
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
2289
|
+
const url = new URL(`${server.replace(/\/$/, "")}/v1/events`);
|
|
2290
|
+
if ((options.afterSequence ?? 0) > 0) url.searchParams.set("afterSequence", String(options.afterSequence));
|
|
2291
|
+
if (options.sessionId) url.searchParams.set("sessionId", options.sessionId);
|
|
2292
|
+
return {
|
|
2293
|
+
url: url.toString(),
|
|
2294
|
+
init: { headers, signal }
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
function terminalEventReconnectDelayMs(attempt) {
|
|
2298
|
+
return Math.min(500 * 2 ** Math.max(0, attempt), 1e4);
|
|
2299
|
+
}
|
|
2300
|
+
async function readTerminalEventStream(response, activeSessionId, onEvent, signal, onReady) {
|
|
2301
|
+
validateTerminalEventResponse(response);
|
|
2302
|
+
const reader = response.body.getReader();
|
|
2303
|
+
const decoder = new TextDecoder();
|
|
2304
|
+
let buffer = "";
|
|
2305
|
+
while (!signal?.aborted) {
|
|
2306
|
+
const { done, value } = await reader.read();
|
|
2307
|
+
if (done) break;
|
|
2308
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2309
|
+
let boundary = buffer.indexOf("\n\n");
|
|
2310
|
+
while (boundary >= 0) {
|
|
2311
|
+
const raw = buffer.slice(0, boundary);
|
|
2312
|
+
buffer = buffer.slice(boundary + 2);
|
|
2313
|
+
handleTerminalEventFrame(raw, activeSessionId, onEvent, onReady);
|
|
2314
|
+
boundary = buffer.indexOf("\n\n");
|
|
2315
|
+
}
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
function validateTerminalEventResponse(response) {
|
|
2319
|
+
if (!response.ok) {
|
|
2320
|
+
throw new Error(`event stream failed: ${response.status} ${response.statusText || "HTTP error"}`);
|
|
2321
|
+
}
|
|
2322
|
+
if (!response.body) {
|
|
2323
|
+
throw new Error("event stream did not return a response body");
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
async function openTerminalEvents(input2) {
|
|
2327
|
+
const controller = new AbortController();
|
|
2328
|
+
const fetchImpl = input2.fetchImpl ?? fetch;
|
|
2329
|
+
const reconnectDelayMs = input2.reconnectDelayMs ?? terminalEventReconnectDelayMs;
|
|
2330
|
+
const initialReady = createDeferred();
|
|
2331
|
+
let sessionReady = initialReady;
|
|
2332
|
+
let requestedSessionId = input2.activeSessionId();
|
|
2333
|
+
let generation = 0;
|
|
2334
|
+
let readyGeneration = -1;
|
|
2335
|
+
let activeRequest = null;
|
|
2336
|
+
controller.ready = initialReady.promise;
|
|
2337
|
+
controller.switchSession = (sessionId) => {
|
|
2338
|
+
if (controller.signal.aborted) return Promise.reject(new Error("event stream is closed"));
|
|
2339
|
+
if (sessionId === requestedSessionId && readyGeneration === generation) return Promise.resolve();
|
|
2340
|
+
requestedSessionId = sessionId;
|
|
2341
|
+
generation += 1;
|
|
2342
|
+
sessionReady.reject(new Error("event stream session changed before becoming ready"));
|
|
2343
|
+
sessionReady = createDeferred();
|
|
2344
|
+
activeRequest?.abort();
|
|
2345
|
+
return sessionReady.promise;
|
|
2346
|
+
};
|
|
2347
|
+
controller.signal.addEventListener(
|
|
2348
|
+
"abort",
|
|
2349
|
+
() => {
|
|
2350
|
+
activeRequest?.abort();
|
|
2351
|
+
const error = new Error("event stream aborted before connecting");
|
|
2352
|
+
initialReady.reject(error);
|
|
2353
|
+
sessionReady.reject(error);
|
|
2354
|
+
},
|
|
2355
|
+
{ once: true }
|
|
2356
|
+
);
|
|
2357
|
+
controller.ready.catch(() => void 0);
|
|
2358
|
+
let lastAppliedSequence = 0;
|
|
2359
|
+
void (async () => {
|
|
2360
|
+
let attempt = 0;
|
|
2361
|
+
while (!controller.signal.aborted) {
|
|
2362
|
+
const requestGeneration = generation;
|
|
2363
|
+
const requestSessionId = requestedSessionId;
|
|
2364
|
+
const requestController = new AbortController();
|
|
2365
|
+
activeRequest = requestController;
|
|
2366
|
+
input2.onStatus?.({ state: "connecting", attempt });
|
|
2367
|
+
try {
|
|
2368
|
+
const request = terminalEventStreamRequest(input2.server, input2.token, requestController.signal, {
|
|
2369
|
+
afterSequence: lastAppliedSequence,
|
|
2370
|
+
sessionId: requestSessionId
|
|
2371
|
+
});
|
|
2372
|
+
const response = await fetchImpl(request.url, request.init);
|
|
2373
|
+
validateTerminalEventResponse(response);
|
|
2374
|
+
input2.onStatus?.({ state: "connected", attempt });
|
|
2375
|
+
await readTerminalEventStream(
|
|
2376
|
+
response,
|
|
2377
|
+
() => requestSessionId,
|
|
2378
|
+
(event) => {
|
|
2379
|
+
const sequence = event.sequence ?? 0;
|
|
2380
|
+
if (sequence > 0 && sequence <= lastAppliedSequence) return;
|
|
2381
|
+
if (sequence > 0) lastAppliedSequence = sequence;
|
|
2382
|
+
input2.onEvent(event);
|
|
2383
|
+
},
|
|
2384
|
+
requestController.signal,
|
|
2385
|
+
() => {
|
|
2386
|
+
if (requestGeneration !== generation) return;
|
|
2387
|
+
readyGeneration = requestGeneration;
|
|
2388
|
+
initialReady.resolve();
|
|
2389
|
+
sessionReady.resolve();
|
|
2390
|
+
}
|
|
2391
|
+
);
|
|
2392
|
+
if (controller.signal.aborted) break;
|
|
2393
|
+
if (requestGeneration !== generation) {
|
|
2394
|
+
attempt = 0;
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
throw new Error("event stream ended");
|
|
2398
|
+
} catch (error) {
|
|
2399
|
+
if (controller.signal.aborted) break;
|
|
2400
|
+
if (requestGeneration !== generation) {
|
|
2401
|
+
attempt = 0;
|
|
2402
|
+
continue;
|
|
2403
|
+
}
|
|
2404
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2405
|
+
const nextDelayMs = reconnectDelayMs(attempt);
|
|
2406
|
+
input2.onStatus?.({ state: "disconnected", attempt, message, nextDelayMs });
|
|
2407
|
+
await abortableDelay(nextDelayMs, requestController.signal);
|
|
2408
|
+
if (requestGeneration !== generation) {
|
|
2409
|
+
attempt = 0;
|
|
2410
|
+
continue;
|
|
2411
|
+
}
|
|
2412
|
+
attempt += 1;
|
|
2413
|
+
} finally {
|
|
2414
|
+
if (activeRequest === requestController) activeRequest = null;
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
})().catch(() => void 0);
|
|
2418
|
+
return controller;
|
|
2419
|
+
}
|
|
2420
|
+
function createDeferred() {
|
|
2421
|
+
let settled = false;
|
|
2422
|
+
let resolvePromise;
|
|
2423
|
+
let rejectPromise;
|
|
2424
|
+
const promise = new Promise((resolve, reject) => {
|
|
2425
|
+
resolvePromise = resolve;
|
|
2426
|
+
rejectPromise = reject;
|
|
2427
|
+
});
|
|
2428
|
+
promise.catch(() => void 0);
|
|
2429
|
+
return {
|
|
2430
|
+
promise,
|
|
2431
|
+
resolve() {
|
|
2432
|
+
if (settled) return;
|
|
2433
|
+
settled = true;
|
|
2434
|
+
resolvePromise();
|
|
2435
|
+
},
|
|
2436
|
+
reject(error) {
|
|
2437
|
+
if (settled) return;
|
|
2438
|
+
settled = true;
|
|
2439
|
+
rejectPromise(error);
|
|
2440
|
+
}
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
function handleTerminalEventFrame(raw, activeSessionId, onEvent, onReady) {
|
|
2444
|
+
const eventName = raw.split("\n").find((line) => line.startsWith("event: "))?.slice(7);
|
|
2445
|
+
if (eventName === "ready") {
|
|
2446
|
+
onReady?.();
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
const dataLine = raw.split("\n").find((line) => line.startsWith("data: "));
|
|
2450
|
+
if (!dataLine) return;
|
|
2451
|
+
try {
|
|
2452
|
+
const event = JSON.parse(dataLine.slice(6));
|
|
2453
|
+
if (typeof event.name !== "string") return;
|
|
2454
|
+
if (!event.sessionId || event.sessionId === activeSessionId()) onEvent(event);
|
|
2455
|
+
} catch {
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
async function abortableDelay(ms, signal) {
|
|
2459
|
+
if (ms <= 0 || signal.aborted) return;
|
|
2460
|
+
await new Promise((resolve) => {
|
|
2461
|
+
const timer = setTimeout(resolve, ms);
|
|
2462
|
+
signal.addEventListener(
|
|
2463
|
+
"abort",
|
|
2464
|
+
() => {
|
|
2465
|
+
clearTimeout(timer);
|
|
2466
|
+
resolve();
|
|
2467
|
+
},
|
|
2468
|
+
{ once: true }
|
|
2469
|
+
);
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
// ../terminal/src/interactive-state.ts
|
|
2474
|
+
var PENDING_TURN = "<submitting>";
|
|
2475
|
+
function createTerminalInteractiveState(initialSessionId = null) {
|
|
2476
|
+
const activeTurns = /* @__PURE__ */ new Map();
|
|
2477
|
+
let activeSessionId = initialSessionId;
|
|
2478
|
+
let ready = false;
|
|
2479
|
+
let switching = false;
|
|
2480
|
+
let stopping = false;
|
|
2481
|
+
function snapshot() {
|
|
2482
|
+
const activeTurnId = activeSessionId ? activeTurns.get(activeSessionId) ?? null : null;
|
|
2483
|
+
const phase = stopping ? "stopping" : switching ? "switching" : !ready ? "starting" : activeTurnId ? "running" : "ready";
|
|
2484
|
+
return {
|
|
2485
|
+
phase,
|
|
2486
|
+
activeSessionId,
|
|
2487
|
+
activeTurnId: activeTurnId === PENDING_TURN ? null : activeTurnId
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
function blocked(action) {
|
|
2491
|
+
const state = snapshot();
|
|
2492
|
+
if (state.phase === "ready") return { ok: true };
|
|
2493
|
+
const message = state.phase === "starting" ? `Wait for terminal startup and event recovery before ${action}.` : state.phase === "running" ? `A turn is already running. Interrupt or wait for it before ${action}.` : state.phase === "switching" ? `Wait for the session switch to finish before ${action}.` : `The terminal is stopping and cannot ${action}.`;
|
|
2494
|
+
return { ok: false, message };
|
|
2495
|
+
}
|
|
2496
|
+
function completeStartup(sessionId) {
|
|
2497
|
+
if (stopping) return;
|
|
2498
|
+
activeSessionId = sessionId;
|
|
2499
|
+
ready = true;
|
|
2500
|
+
}
|
|
2501
|
+
function beginTurn(sessionId) {
|
|
2502
|
+
const allowed = blocked("starting another turn");
|
|
2503
|
+
if (!allowed.ok) return allowed;
|
|
2504
|
+
if (sessionId !== activeSessionId) return { ok: false, message: "The active session changed; retry the turn." };
|
|
2505
|
+
activeTurns.set(sessionId, PENDING_TURN);
|
|
2506
|
+
return { ok: true };
|
|
2507
|
+
}
|
|
2508
|
+
function failTurnSubmission(sessionId) {
|
|
2509
|
+
if (activeTurns.get(sessionId) === PENDING_TURN) activeTurns.delete(sessionId);
|
|
2510
|
+
}
|
|
2511
|
+
function applyRuntimeEvent(event) {
|
|
2512
|
+
const sessionId = event.sessionId ?? activeSessionId;
|
|
2513
|
+
if (!sessionId) return;
|
|
2514
|
+
if (event.name === "turn.started") {
|
|
2515
|
+
activeTurns.set(sessionId, event.turnId ?? PENDING_TURN);
|
|
2516
|
+
return;
|
|
2517
|
+
}
|
|
2518
|
+
if (event.name !== "turn.completed" && event.name !== "turn.failed" && event.name !== "turn.interrupted") return;
|
|
2519
|
+
const activeTurnId = activeTurns.get(sessionId);
|
|
2520
|
+
if (!activeTurnId) return;
|
|
2521
|
+
if (activeTurnId !== PENDING_TURN && event.turnId && event.turnId !== activeTurnId) return;
|
|
2522
|
+
activeTurns.delete(sessionId);
|
|
2523
|
+
}
|
|
2524
|
+
function beginSessionSwitch() {
|
|
2525
|
+
const allowed = blocked("switching sessions");
|
|
2526
|
+
if (!allowed.ok) return allowed;
|
|
2527
|
+
switching = true;
|
|
2528
|
+
return { ok: true };
|
|
2529
|
+
}
|
|
2530
|
+
function completeSessionSwitch(sessionId) {
|
|
2531
|
+
activeSessionId = sessionId;
|
|
2532
|
+
switching = false;
|
|
2533
|
+
}
|
|
2534
|
+
function failSessionSwitch() {
|
|
2535
|
+
switching = false;
|
|
2536
|
+
}
|
|
2537
|
+
function beginStopping() {
|
|
2538
|
+
stopping = true;
|
|
2539
|
+
}
|
|
2540
|
+
return {
|
|
2541
|
+
applyRuntimeEvent,
|
|
2542
|
+
beginSessionSwitch,
|
|
2543
|
+
beginStopping,
|
|
2544
|
+
beginTurn,
|
|
2545
|
+
blocked,
|
|
2546
|
+
completeSessionSwitch,
|
|
2547
|
+
completeStartup,
|
|
2548
|
+
failSessionSwitch,
|
|
2549
|
+
failTurnSubmission,
|
|
2550
|
+
snapshot
|
|
2551
|
+
};
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
// ../terminal/src/task-scheduler.ts
|
|
2555
|
+
function createSerialTaskScheduler() {
|
|
2556
|
+
let tail = Promise.resolve();
|
|
2557
|
+
return {
|
|
2558
|
+
run(task) {
|
|
2559
|
+
const runTask = () => Promise.resolve().then(task);
|
|
2560
|
+
const next = tail.then(runTask, runTask);
|
|
2561
|
+
tail = next.catch(() => void 0);
|
|
2562
|
+
return next;
|
|
2563
|
+
}
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
function createLatestWinsTaskScheduler(options = {}) {
|
|
2567
|
+
const delayMs = Math.max(0, options.delayMs ?? 16);
|
|
2568
|
+
const setTimer = options.setTimer ?? setTimeout;
|
|
2569
|
+
const clearTimer = options.clearTimer ?? clearTimeout;
|
|
2570
|
+
let latestTask = null;
|
|
2571
|
+
let timer = null;
|
|
2572
|
+
function clearPendingTimer() {
|
|
2573
|
+
if (!timer) return;
|
|
2574
|
+
clearTimer(timer);
|
|
2575
|
+
timer = null;
|
|
2576
|
+
}
|
|
2577
|
+
function runLatest() {
|
|
2578
|
+
timer = null;
|
|
2579
|
+
const task = latestTask;
|
|
2580
|
+
latestTask = null;
|
|
2581
|
+
task?.();
|
|
2582
|
+
}
|
|
2583
|
+
return {
|
|
2584
|
+
request(task) {
|
|
2585
|
+
latestTask = task;
|
|
2586
|
+
if (timer) return;
|
|
2587
|
+
timer = setTimer(runLatest, delayMs);
|
|
2588
|
+
},
|
|
2589
|
+
flush() {
|
|
2590
|
+
clearPendingTimer();
|
|
2591
|
+
runLatest();
|
|
2592
|
+
},
|
|
2593
|
+
cancel() {
|
|
2594
|
+
latestTask = null;
|
|
2595
|
+
clearPendingTimer();
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
// ../terminal/src/exit-latch.ts
|
|
2601
|
+
function createTerminalExitLatch() {
|
|
2602
|
+
let requested = false;
|
|
2603
|
+
let resolveWait = null;
|
|
2604
|
+
return {
|
|
2605
|
+
get requested() {
|
|
2606
|
+
return requested;
|
|
2607
|
+
},
|
|
2608
|
+
request() {
|
|
2609
|
+
if (requested) return;
|
|
2610
|
+
requested = true;
|
|
2611
|
+
resolveWait?.();
|
|
2612
|
+
},
|
|
2613
|
+
wait() {
|
|
2614
|
+
if (requested) return Promise.resolve();
|
|
2615
|
+
return new Promise((resolve) => {
|
|
2616
|
+
resolveWait = resolve;
|
|
2617
|
+
if (requested) resolve();
|
|
2618
|
+
});
|
|
2619
|
+
}
|
|
2620
|
+
};
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
// ../terminal/src/one-shot-chat.ts
|
|
2624
|
+
import { readFile } from "node:fs/promises";
|
|
2625
|
+
import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
|
|
2626
|
+
var TerminalOneShotExitError = class extends Error {
|
|
2627
|
+
constructor(message, exitCode) {
|
|
2628
|
+
super(message);
|
|
2629
|
+
this.exitCode = exitCode;
|
|
2630
|
+
this.name = "TerminalOneShotExitError";
|
|
2631
|
+
}
|
|
2632
|
+
exitCode;
|
|
2633
|
+
};
|
|
2634
|
+
var OneShotDeadlineError = class extends Error {
|
|
2635
|
+
constructor(phase, timeoutSec) {
|
|
2636
|
+
super(`Timed out after ${timeoutSec}s during ${phase}.`);
|
|
2637
|
+
this.phase = phase;
|
|
2638
|
+
this.timeoutSec = timeoutSec;
|
|
2639
|
+
this.name = "OneShotDeadlineError";
|
|
2640
|
+
}
|
|
2641
|
+
phase;
|
|
2642
|
+
timeoutSec;
|
|
2643
|
+
};
|
|
2644
|
+
async function runOneShotChat(options, io = {}) {
|
|
2645
|
+
const output2 = io.output ?? defaultOutput;
|
|
2646
|
+
const startedAtMs = Date.now();
|
|
2647
|
+
const startedAt = new Date(startedAtMs).toISOString();
|
|
2648
|
+
const deadline = createOneShotDeadline(options.timeoutSec);
|
|
2649
|
+
const startupWarnings = [];
|
|
2650
|
+
let connection = null;
|
|
2651
|
+
let eventStream = null;
|
|
2652
|
+
let timedOut = false;
|
|
2653
|
+
let activeSessionId = null;
|
|
2654
|
+
let selectedModel = options.model;
|
|
2655
|
+
let accumulator = null;
|
|
2656
|
+
let turnSubmitted = false;
|
|
2657
|
+
try {
|
|
2658
|
+
const prompt = await deadline.run("input loading", resolveOneShotPrompt(options, io.input ?? defaultInput));
|
|
2659
|
+
if (!prompt.trim()) {
|
|
2660
|
+
throw new TerminalOneShotExitError("openpond chat --non-interactive requires --message, --message-file, or --stdin input.", 2);
|
|
2661
|
+
}
|
|
2662
|
+
connection = io.connection ?? await deadline.run(
|
|
2663
|
+
"server startup",
|
|
2664
|
+
ensureServer(options, (line) => startupWarnings.push(line))
|
|
2665
|
+
);
|
|
2666
|
+
const payload = await deadline.run(
|
|
2667
|
+
"bootstrap",
|
|
2668
|
+
apiFetch(connection.server, connection.token, "/v1/bootstrap?refreshCodex=1", {
|
|
2669
|
+
signal: deadline.signal
|
|
2670
|
+
})
|
|
2671
|
+
);
|
|
2672
|
+
const sessionState = await deadline.run("session setup", ensureTerminalChatSession(
|
|
2673
|
+
{ ...connection, signal: deadline.signal },
|
|
2674
|
+
payload,
|
|
2675
|
+
{ ...options, headless: true },
|
|
2676
|
+
options.resume
|
|
2677
|
+
));
|
|
2678
|
+
const readySession = await deadline.run(
|
|
2679
|
+
"workspace readiness",
|
|
2680
|
+
ensureTerminalSessionWorkspaceReady({ ...connection, signal: deadline.signal }, sessionState.session)
|
|
2681
|
+
);
|
|
2682
|
+
activeSessionId = sessionState.sessionId;
|
|
2683
|
+
if (readySession?.cwd) options.cwd = readySession.cwd;
|
|
2684
|
+
options.provider = sessionState.provider;
|
|
2685
|
+
options.model = sessionState.model;
|
|
2686
|
+
selectedModel = sessionState.model;
|
|
2687
|
+
const modelId = activeModelId(options, payload.providers);
|
|
2688
|
+
if (!modelId) {
|
|
2689
|
+
throw new Error(`No model selected for ${providerLabel(payload.providers, options.provider)}.`);
|
|
2690
|
+
}
|
|
2691
|
+
selectedModel = modelId;
|
|
2692
|
+
if (!options.json) {
|
|
2693
|
+
for (const line of startupWarnings) output2.write(`${line}
|
|
2694
|
+
`);
|
|
2695
|
+
output2.write(`${terminalSessionHeading(payload.providers, options)}
|
|
2696
|
+
`);
|
|
2697
|
+
}
|
|
2698
|
+
const activeAccumulator = createOneShotAccumulator({ maxOutputBytes: options.maxOutputBytes });
|
|
2699
|
+
accumulator = activeAccumulator;
|
|
2700
|
+
let resolveTerminalEvent = (_event) => void 0;
|
|
2701
|
+
const terminalEventPromise = new Promise((resolve) => {
|
|
2702
|
+
resolveTerminalEvent = resolve;
|
|
2703
|
+
});
|
|
2704
|
+
eventStream = openOneShotTerminalEvents({
|
|
2705
|
+
server: connection.server,
|
|
2706
|
+
token: connection.token,
|
|
2707
|
+
activeSessionId,
|
|
2708
|
+
onEvent: (event) => {
|
|
2709
|
+
activeAccumulator.apply(event);
|
|
2710
|
+
if (!options.json) output2.write(humanOneShotEventText(event));
|
|
2711
|
+
if (isTerminalTurnEvent(event)) resolveTerminalEvent(event);
|
|
2712
|
+
},
|
|
2713
|
+
onStatus: (message) => {
|
|
2714
|
+
if (!options.json) output2.write(message);
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
await deadline.run("event stream readiness", eventStream.ready);
|
|
2718
|
+
await deadline.run("turn submission", apiFetch(connection.server, connection.token, `/v1/sessions/${activeSessionId}/turns`, {
|
|
2719
|
+
method: "POST",
|
|
2720
|
+
body: JSON.stringify({
|
|
2721
|
+
prompt,
|
|
2722
|
+
...options.cwdExplicit || !options.project && !options.resume ? { cwd: options.cwd } : {},
|
|
2723
|
+
model: modelId,
|
|
2724
|
+
modelRef: activeModelRef(options, payload.providers),
|
|
2725
|
+
approvalPolicy: options.yes ? "never" : options.approvalPolicy,
|
|
2726
|
+
sandbox: options.sandbox,
|
|
2727
|
+
metadata: {
|
|
2728
|
+
openpondTerminalMode: "one-shot",
|
|
2729
|
+
openpondTerminal: {
|
|
2730
|
+
mode: "one-shot",
|
|
2731
|
+
nonInteractive: true,
|
|
2732
|
+
sandbox: options.sandbox
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}),
|
|
2736
|
+
signal: deadline.signal
|
|
2737
|
+
}));
|
|
2738
|
+
turnSubmitted = true;
|
|
2739
|
+
const terminal = await deadline.run("turn completion", terminalEventPromise);
|
|
2740
|
+
const result = activeAccumulator.result({
|
|
2741
|
+
terminal: terminal.name,
|
|
2742
|
+
sessionId: activeSessionId,
|
|
2743
|
+
provider: options.provider,
|
|
2744
|
+
model: modelId,
|
|
2745
|
+
cwd: options.cwd,
|
|
2746
|
+
startedAt,
|
|
2747
|
+
startedAtMs,
|
|
2748
|
+
error: terminal.error ?? (terminal.name === "turn.failed" ? terminal.output ?? "Turn failed." : null)
|
|
2749
|
+
});
|
|
2750
|
+
writeOneShotResult(output2, options, result);
|
|
2751
|
+
if (result.status !== "completed") process.exitCode = 1;
|
|
2752
|
+
return result;
|
|
2753
|
+
} catch (error) {
|
|
2754
|
+
if (error instanceof TerminalOneShotExitError) throw error;
|
|
2755
|
+
if (error instanceof OneShotDeadlineError) {
|
|
2756
|
+
timedOut = true;
|
|
2757
|
+
if (connection && activeSessionId && turnSubmitted) {
|
|
2758
|
+
await interruptOneShotTurn(connection.server, connection.token, activeSessionId);
|
|
2759
|
+
}
|
|
2760
|
+
const result2 = (accumulator ?? createOneShotAccumulator({ maxOutputBytes: options.maxOutputBytes })).result({
|
|
2761
|
+
terminal: "timeout",
|
|
2762
|
+
sessionId: activeSessionId,
|
|
2763
|
+
provider: options.provider,
|
|
2764
|
+
model: selectedModel,
|
|
2765
|
+
cwd: options.cwd,
|
|
2766
|
+
startedAt,
|
|
2767
|
+
startedAtMs,
|
|
2768
|
+
error: error.message
|
|
2769
|
+
});
|
|
2770
|
+
writeOneShotResult(output2, options, result2);
|
|
2771
|
+
process.exitCode = 124;
|
|
2772
|
+
return result2;
|
|
2773
|
+
}
|
|
2774
|
+
const result = (accumulator ?? createOneShotAccumulator({ maxOutputBytes: options.maxOutputBytes })).result({
|
|
2775
|
+
terminal: "error",
|
|
2776
|
+
sessionId: activeSessionId,
|
|
2777
|
+
provider: options.provider,
|
|
2778
|
+
model: selectedModel,
|
|
2779
|
+
cwd: options.cwd,
|
|
2780
|
+
startedAt,
|
|
2781
|
+
startedAtMs,
|
|
2782
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2783
|
+
});
|
|
2784
|
+
writeOneShotResult(output2, options, result);
|
|
2785
|
+
process.exitCode = 1;
|
|
2786
|
+
return result;
|
|
2787
|
+
} finally {
|
|
2788
|
+
deadline.dispose();
|
|
2789
|
+
const activeEventStream = eventStream;
|
|
2790
|
+
activeEventStream?.abort();
|
|
2791
|
+
if (!io.connection) await stopManagedServer();
|
|
2792
|
+
if (timedOut && !options.json) output2.write("\n[turn timeout]\n");
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
function createOneShotDeadline(timeoutSec) {
|
|
2796
|
+
const controller = new AbortController();
|
|
2797
|
+
let phase = "startup";
|
|
2798
|
+
let timer = null;
|
|
2799
|
+
let rejectTimeout = (_error) => void 0;
|
|
2800
|
+
const timeout = new Promise((_resolve, reject) => {
|
|
2801
|
+
rejectTimeout = reject;
|
|
2802
|
+
});
|
|
2803
|
+
timeout.catch(() => void 0);
|
|
2804
|
+
if (timeoutSec) {
|
|
2805
|
+
timer = setTimeout(() => {
|
|
2806
|
+
controller.abort();
|
|
2807
|
+
rejectTimeout(new OneShotDeadlineError(phase, timeoutSec));
|
|
2808
|
+
}, timeoutSec * 1e3);
|
|
2809
|
+
}
|
|
2810
|
+
return {
|
|
2811
|
+
signal: controller.signal,
|
|
2812
|
+
async run(nextPhase, operation) {
|
|
2813
|
+
phase = nextPhase;
|
|
2814
|
+
return timeoutSec ? Promise.race([operation, timeout]) : operation;
|
|
2815
|
+
},
|
|
2816
|
+
dispose() {
|
|
2817
|
+
if (timer) clearTimeout(timer);
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
}
|
|
2821
|
+
function createOneShotAccumulator(options = {}) {
|
|
2822
|
+
let total = 0;
|
|
2823
|
+
let commands = 0;
|
|
2824
|
+
let workspaceActions = 0;
|
|
2825
|
+
let finalMessage = "";
|
|
2826
|
+
let finalMessageBytes = 0;
|
|
2827
|
+
let finalMessageTruncated = false;
|
|
2828
|
+
let turnId = null;
|
|
2829
|
+
let usage = null;
|
|
2830
|
+
const maxOutputBytes = options.maxOutputBytes ?? null;
|
|
2831
|
+
return {
|
|
2832
|
+
apply(event) {
|
|
2833
|
+
total += 1;
|
|
2834
|
+
if (event.turnId) turnId = event.turnId;
|
|
2835
|
+
if (event.name === "assistant.delta" && event.output && !finalMessageTruncated) {
|
|
2836
|
+
const next = appendWithinByteLimit(finalMessage, finalMessageBytes, event.output, maxOutputBytes);
|
|
2837
|
+
finalMessage = next.text;
|
|
2838
|
+
finalMessageBytes = next.bytes;
|
|
2839
|
+
finalMessageTruncated = next.truncated;
|
|
2840
|
+
}
|
|
2841
|
+
if (event.name === "command.output" || event.name === "tool.completed" && event.action === "exec_command") {
|
|
2842
|
+
commands += 1;
|
|
2843
|
+
}
|
|
2844
|
+
if (event.name === "workspace_action" || event.name === "workspace_action_result") workspaceActions += 1;
|
|
2845
|
+
const usageSnapshot = contextUsageFromEvent(event);
|
|
2846
|
+
if (usageSnapshot) usage = usageSnapshot;
|
|
2847
|
+
},
|
|
2848
|
+
result(input2) {
|
|
2849
|
+
const finishedAtMs = Date.now();
|
|
2850
|
+
const terminal = input2.terminal;
|
|
2851
|
+
return {
|
|
2852
|
+
status: terminalStatus(terminal),
|
|
2853
|
+
sessionId: input2.sessionId,
|
|
2854
|
+
turnId,
|
|
2855
|
+
provider: input2.provider,
|
|
2856
|
+
model: input2.model,
|
|
2857
|
+
cwd: input2.cwd,
|
|
2858
|
+
startedAt: input2.startedAt,
|
|
2859
|
+
finishedAt: new Date(finishedAtMs).toISOString(),
|
|
2860
|
+
durationMs: Math.max(0, finishedAtMs - input2.startedAtMs),
|
|
2861
|
+
finalMessage,
|
|
2862
|
+
output: {
|
|
2863
|
+
finalMessageBytes,
|
|
2864
|
+
truncated: finalMessageTruncated,
|
|
2865
|
+
maxOutputBytes
|
|
2866
|
+
},
|
|
2867
|
+
events: {
|
|
2868
|
+
terminal,
|
|
2869
|
+
total,
|
|
2870
|
+
commands,
|
|
2871
|
+
workspaceActions
|
|
2872
|
+
},
|
|
2873
|
+
usage,
|
|
2874
|
+
error: input2.error
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
function contextUsageFromEvent(event) {
|
|
2880
|
+
if (event.name !== "session.context.updated") return null;
|
|
2881
|
+
const parsed = ContextUsageSnapshotSchema.safeParse(event.data);
|
|
2882
|
+
if (!parsed.success) return null;
|
|
2883
|
+
if (parsed.data.source === "heuristic") return null;
|
|
2884
|
+
return parsed.data;
|
|
2885
|
+
}
|
|
2886
|
+
function appendWithinByteLimit(current, currentBytes, chunk, maxBytes) {
|
|
2887
|
+
if (maxBytes === null) {
|
|
2888
|
+
return {
|
|
2889
|
+
text: current + chunk,
|
|
2890
|
+
bytes: currentBytes + utf8Bytes2(chunk),
|
|
2891
|
+
truncated: false
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
let text = current;
|
|
2895
|
+
let bytes = currentBytes;
|
|
2896
|
+
for (const char of chunk) {
|
|
2897
|
+
const charBytes = utf8Bytes2(char);
|
|
2898
|
+
if (bytes + charBytes > maxBytes) {
|
|
2899
|
+
return { text, bytes, truncated: true };
|
|
2900
|
+
}
|
|
2901
|
+
text += char;
|
|
2902
|
+
bytes += charBytes;
|
|
2903
|
+
}
|
|
2904
|
+
return { text, bytes, truncated: false };
|
|
2905
|
+
}
|
|
2906
|
+
function utf8Bytes2(value) {
|
|
2907
|
+
return new TextEncoder().encode(value).byteLength;
|
|
2908
|
+
}
|
|
2909
|
+
async function resolveOneShotPrompt(options, input2) {
|
|
2910
|
+
const parts = [];
|
|
2911
|
+
const inputIsTty = Boolean(input2.isTTY);
|
|
2912
|
+
const explicitSources = [
|
|
2913
|
+
options.message !== null ? "--message" : null,
|
|
2914
|
+
options.messageFile !== null ? "--message-file" : null,
|
|
2915
|
+
options.stdin ? "--stdin" : null
|
|
2916
|
+
].filter((source) => source !== null);
|
|
2917
|
+
if (explicitSources.length > 1) {
|
|
2918
|
+
throw new TerminalOneShotExitError(
|
|
2919
|
+
`openpond chat --non-interactive accepts exactly one instruction source; received ${explicitSources.join(", ")}.`,
|
|
2920
|
+
2
|
|
2921
|
+
);
|
|
2922
|
+
}
|
|
2923
|
+
if (options.message !== null) parts.push(options.message);
|
|
2924
|
+
if (options.messageFile) parts.push(await readOneShotMessageFile(options.messageFile));
|
|
2925
|
+
if (options.stdin || options.nonInteractive && !inputIsTty && !options.message && !options.messageFile) {
|
|
2926
|
+
parts.push(await readAll(input2));
|
|
2927
|
+
}
|
|
2928
|
+
return parts.join("\n\n").trimEnd();
|
|
2929
|
+
}
|
|
2930
|
+
async function readOneShotMessageFile(filePath) {
|
|
2931
|
+
try {
|
|
2932
|
+
return await readFile(filePath, "utf8");
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
2935
|
+
throw new TerminalOneShotExitError(`openpond chat --message-file could not read ${filePath}: ${detail}`, 2);
|
|
2936
|
+
}
|
|
2937
|
+
}
|
|
2938
|
+
async function readAll(input2) {
|
|
2939
|
+
let text = "";
|
|
2940
|
+
input2.setEncoding("utf8");
|
|
2941
|
+
for await (const chunk of input2) text += String(chunk);
|
|
2942
|
+
return text;
|
|
2943
|
+
}
|
|
2944
|
+
function openOneShotTerminalEvents(input2) {
|
|
2945
|
+
const pending = openTerminalEvents({
|
|
2946
|
+
server: input2.server,
|
|
2947
|
+
token: input2.token,
|
|
2948
|
+
activeSessionId: () => input2.activeSessionId,
|
|
2949
|
+
onEvent: input2.onEvent,
|
|
2950
|
+
onStatus: (status) => {
|
|
2951
|
+
if (status.state === "disconnected") {
|
|
2952
|
+
input2.onStatus(`
|
|
2953
|
+
[event stream] reconnecting in ${Math.ceil(status.nextDelayMs / 1e3)}s: ${status.message}
|
|
2954
|
+
`);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
});
|
|
2958
|
+
const fallback = new AbortController();
|
|
2959
|
+
fallback.ready = pending.then((controller) => controller.ready);
|
|
2960
|
+
fallback.switchSession = (sessionId) => pending.then((controller) => controller.switchSession(sessionId));
|
|
2961
|
+
pending.then((controller) => {
|
|
2962
|
+
if (fallback.signal.aborted) controller.abort();
|
|
2963
|
+
else {
|
|
2964
|
+
fallback.signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
2965
|
+
}
|
|
2966
|
+
}).catch(() => void 0);
|
|
2967
|
+
return fallback;
|
|
2968
|
+
}
|
|
2969
|
+
async function interruptOneShotTurn(server, token, sessionId) {
|
|
2970
|
+
await apiFetch(server, token, `/v1/sessions/${sessionId}/turns/interrupt`, { method: "POST" }).catch(() => void 0);
|
|
2971
|
+
}
|
|
2972
|
+
function writeOneShotResult(output2, options, result) {
|
|
2973
|
+
if (options.json) {
|
|
2974
|
+
output2.write(`${JSON.stringify(result, null, 2)}
|
|
2975
|
+
`);
|
|
2976
|
+
return;
|
|
2977
|
+
}
|
|
2978
|
+
output2.write(`
|
|
2979
|
+
[turn ${result.status}]
|
|
2980
|
+
`);
|
|
2981
|
+
}
|
|
2982
|
+
function humanOneShotEventText(event) {
|
|
2983
|
+
if (event.name === "assistant.delta") return event.output ?? "";
|
|
2984
|
+
if (event.name === "approval.requested") return `
|
|
2985
|
+
[approval] ${event.output ?? event.action ?? "request"}
|
|
2986
|
+
`;
|
|
2987
|
+
if (event.name === "command.output" && event.output) return `
|
|
2988
|
+
[command] ${event.output}
|
|
2989
|
+
`;
|
|
2990
|
+
if (event.name.startsWith("workspace_")) return `
|
|
2991
|
+
[openpond] ${event.output ?? event.action ?? event.name}
|
|
2992
|
+
`;
|
|
2993
|
+
return "";
|
|
2994
|
+
}
|
|
2995
|
+
function isTerminalTurnEvent(event) {
|
|
2996
|
+
return event.name === "turn.completed" || event.name === "turn.failed" || event.name === "turn.interrupted";
|
|
2997
|
+
}
|
|
2998
|
+
function terminalStatus(terminal) {
|
|
2999
|
+
if (terminal === "turn.completed") return "completed";
|
|
3000
|
+
if (terminal === "turn.interrupted") return "interrupted";
|
|
3001
|
+
if (terminal === "timeout") return "timeout";
|
|
3002
|
+
return "failed";
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
// ../terminal/src/index.ts
|
|
3006
|
+
async function interruptTurn(server, token, sessionId) {
|
|
3007
|
+
await apiFetch(server, token, `/v1/sessions/${sessionId}/turns/interrupt`, { method: "POST" });
|
|
3008
|
+
}
|
|
3009
|
+
function openUrlCommand(url) {
|
|
3010
|
+
if (process.platform === "darwin") return { command: "open", args: [url] };
|
|
3011
|
+
if (process.platform === "win32") return { command: "cmd", args: ["/c", "start", "", url] };
|
|
3012
|
+
return { command: "xdg-open", args: [url] };
|
|
3013
|
+
}
|
|
3014
|
+
function openUrlDetached(url) {
|
|
3015
|
+
const { command, args } = openUrlCommand(url);
|
|
3016
|
+
const child = spawn2(command, args, {
|
|
3017
|
+
detached: true,
|
|
3018
|
+
stdio: "ignore"
|
|
3019
|
+
});
|
|
3020
|
+
child.unref();
|
|
3021
|
+
}
|
|
3022
|
+
async function chat(options) {
|
|
3023
|
+
const mode = resolveTerminalChatMode(options, {
|
|
3024
|
+
inputIsTTY: Boolean(input.isTTY),
|
|
3025
|
+
outputIsTTY: Boolean(output.isTTY)
|
|
3026
|
+
});
|
|
3027
|
+
if (mode === "one-shot") {
|
|
3028
|
+
await runOneShotChat(options, { input, output });
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
if (mode === "line-mode") {
|
|
3032
|
+
await lineModeChat(options);
|
|
3033
|
+
return;
|
|
3034
|
+
}
|
|
3035
|
+
let transcript = [];
|
|
3036
|
+
let composer = createComposer();
|
|
3037
|
+
let slashSelectedIndex = 0;
|
|
3038
|
+
let slashLastQuery = null;
|
|
3039
|
+
let scrollOffset = 0;
|
|
3040
|
+
let activeSessionId = options.resume;
|
|
3041
|
+
let activeAgentId = null;
|
|
3042
|
+
let payload = null;
|
|
3043
|
+
let eventStream = null;
|
|
3044
|
+
const exitLatch = createTerminalExitLatch();
|
|
3045
|
+
let connection = null;
|
|
3046
|
+
let pendingCommandApproval = null;
|
|
3047
|
+
let permissionQuestion = null;
|
|
3048
|
+
let permissionQuestionSelectedIndex = 0;
|
|
3049
|
+
let notice = null;
|
|
3050
|
+
let eventStreamNotice = null;
|
|
3051
|
+
const commandScheduler = createSerialTaskScheduler();
|
|
3052
|
+
const resizeRenderScheduler = createLatestWinsTaskScheduler();
|
|
3053
|
+
const interaction = createTerminalInteractiveState(activeSessionId);
|
|
3054
|
+
const renderer = new TerminalRenderer(output, () => {
|
|
3055
|
+
resizeRenderScheduler.request(() => {
|
|
3056
|
+
renderer.fullRedraw();
|
|
3057
|
+
render();
|
|
3058
|
+
});
|
|
3059
|
+
});
|
|
3060
|
+
function status() {
|
|
3061
|
+
return {
|
|
3062
|
+
provider: providerLabel(payload?.providers, options.provider),
|
|
3063
|
+
model: modelLabel(payload?.providers, options),
|
|
3064
|
+
cwd: options.cwd,
|
|
3065
|
+
profile: payload ? profileLabel(payload) : "loading",
|
|
3066
|
+
agent: activeAgentId,
|
|
3067
|
+
running: interaction.snapshot().phase === "running",
|
|
3068
|
+
sessionId: activeSessionId,
|
|
3069
|
+
notice: notice ?? eventStreamNotice
|
|
3070
|
+
};
|
|
3071
|
+
}
|
|
3072
|
+
function render() {
|
|
3073
|
+
flushCommittedItems();
|
|
3074
|
+
renderer.render({
|
|
3075
|
+
transcript,
|
|
3076
|
+
composer,
|
|
3077
|
+
slashMenu: activeSlashMenu(),
|
|
3078
|
+
status: status(),
|
|
3079
|
+
scrollOffset
|
|
3080
|
+
});
|
|
3081
|
+
}
|
|
3082
|
+
const eventRenderScheduler = createTerminalRenderScheduler(render);
|
|
3083
|
+
function activeSlashMenu() {
|
|
3084
|
+
if (permissionQuestion) {
|
|
3085
|
+
permissionQuestionSelectedIndex = Math.min(
|
|
3086
|
+
Math.max(0, permissionQuestionSelectedIndex),
|
|
3087
|
+
TERMINAL_PERMISSION_QUESTION_CHOICES.length - 1
|
|
3088
|
+
);
|
|
3089
|
+
return {
|
|
3090
|
+
items: TERMINAL_PERMISSION_QUESTION_CHOICES,
|
|
3091
|
+
selectedIndex: permissionQuestionSelectedIndex
|
|
3092
|
+
};
|
|
3093
|
+
}
|
|
3094
|
+
const appQuery = installAppQuery(composer.text);
|
|
3095
|
+
if (appQuery !== null) {
|
|
3096
|
+
const queryKey = `install:${appQuery}`;
|
|
3097
|
+
if (queryKey !== slashLastQuery) {
|
|
3098
|
+
slashSelectedIndex = 0;
|
|
3099
|
+
slashLastQuery = queryKey;
|
|
3100
|
+
}
|
|
3101
|
+
const items2 = installAppOptions(appQuery);
|
|
3102
|
+
if (items2.length === 0) return null;
|
|
3103
|
+
slashSelectedIndex = Math.min(Math.max(0, slashSelectedIndex), items2.length - 1);
|
|
3104
|
+
return { items: items2, selectedIndex: slashSelectedIndex };
|
|
3105
|
+
}
|
|
3106
|
+
const query = slashCommandQuery(composer.text);
|
|
3107
|
+
if (query === null) {
|
|
3108
|
+
slashLastQuery = null;
|
|
3109
|
+
return null;
|
|
3110
|
+
}
|
|
3111
|
+
if (query !== slashLastQuery) {
|
|
3112
|
+
slashSelectedIndex = 0;
|
|
3113
|
+
slashLastQuery = query;
|
|
3114
|
+
}
|
|
3115
|
+
const items = SLASH_COMMANDS.filter((command) => command.name.startsWith(query) || command.usage.slice(1).startsWith(query));
|
|
3116
|
+
if (items.length === 0) return null;
|
|
3117
|
+
slashSelectedIndex = Math.min(Math.max(0, slashSelectedIndex), items.length - 1);
|
|
3118
|
+
return { items, selectedIndex: slashSelectedIndex };
|
|
3119
|
+
}
|
|
3120
|
+
function slashCommandQuery(text) {
|
|
3121
|
+
if (!text.startsWith("/") || text.includes("\n")) return null;
|
|
3122
|
+
const body = text.slice(1);
|
|
3123
|
+
if (body.includes(" ")) return null;
|
|
3124
|
+
return body.toLowerCase();
|
|
3125
|
+
}
|
|
3126
|
+
function flushCommittedItems() {
|
|
3127
|
+
const { readyItems, activeItems } = commitReadyTranscriptItems(transcript);
|
|
3128
|
+
if (readyItems.length === 0) return;
|
|
3129
|
+
renderer.commitLines(renderTranscriptItemsForScrollback(readyItems, output.columns || 80));
|
|
3130
|
+
transcript = activeItems;
|
|
3131
|
+
}
|
|
3132
|
+
function addItem(item) {
|
|
3133
|
+
transcript = appendTranscriptItem(transcript, item);
|
|
3134
|
+
scrollOffset = 0;
|
|
3135
|
+
render();
|
|
3136
|
+
}
|
|
3137
|
+
function setNotice(text) {
|
|
3138
|
+
notice = text;
|
|
3139
|
+
render();
|
|
3140
|
+
}
|
|
3141
|
+
function activePendingCommandApproval() {
|
|
3142
|
+
if (pendingCommandApproval?.status === "pending" && pendingCommandApproval.sessionId === activeSessionId) {
|
|
3143
|
+
return pendingCommandApproval;
|
|
3144
|
+
}
|
|
3145
|
+
return latestPendingCommandApproval(payload, activeSessionId);
|
|
3146
|
+
}
|
|
3147
|
+
function openCommandApprovalQuestion(approval) {
|
|
3148
|
+
pendingCommandApproval = approval;
|
|
3149
|
+
permissionQuestion = { approval };
|
|
3150
|
+
permissionQuestionSelectedIndex = 0;
|
|
3151
|
+
setNotice("approval question");
|
|
3152
|
+
addItem(systemItem(formatTerminalCommandApprovalQuestion(approval), "warning"));
|
|
3153
|
+
}
|
|
3154
|
+
async function resolvePermissionQuestion(choice) {
|
|
3155
|
+
if (!connection || !permissionQuestion) return;
|
|
3156
|
+
const approval = permissionQuestion.approval;
|
|
3157
|
+
setNotice("resolving approval");
|
|
3158
|
+
try {
|
|
3159
|
+
await apiFetch(connection.server, connection.token, `/v1/approvals/${encodeURIComponent(approval.id)}`, {
|
|
3160
|
+
method: "POST",
|
|
3161
|
+
body: JSON.stringify({
|
|
3162
|
+
decision: choice === "yes" ? "accept" : choice === "session" ? "acceptForSession" : choice === "skip" ? "cancel" : "decline"
|
|
3163
|
+
})
|
|
3164
|
+
});
|
|
3165
|
+
permissionQuestion = null;
|
|
3166
|
+
if (pendingCommandApproval?.id === approval.id) pendingCommandApproval = null;
|
|
3167
|
+
notice = null;
|
|
3168
|
+
render();
|
|
3169
|
+
} catch (error) {
|
|
3170
|
+
setNotice(null);
|
|
3171
|
+
addItem(systemItem(error instanceof Error ? error.message : String(error), "error"));
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
async function refreshBootstrap(refreshCodex = false) {
|
|
3175
|
+
if (!connection) throw new Error("Not connected");
|
|
3176
|
+
payload = await apiFetch(connection.server, connection.token, `/v1/bootstrap${refreshCodex ? "?refreshCodex=1" : ""}`);
|
|
3177
|
+
activeAgentId = activeAgentId ?? payload.profile.agents[0]?.id ?? null;
|
|
3178
|
+
return payload;
|
|
3179
|
+
}
|
|
3180
|
+
async function submitPrompt(text) {
|
|
3181
|
+
if (!activeSessionId) {
|
|
3182
|
+
addItem(systemItem("Wait for terminal startup before sending a turn.", "warning"));
|
|
3183
|
+
return;
|
|
3184
|
+
}
|
|
3185
|
+
const started = interaction.beginTurn(activeSessionId);
|
|
3186
|
+
if (!started.ok) {
|
|
3187
|
+
addItem(systemItem(started.message, "warning"));
|
|
3188
|
+
return;
|
|
3189
|
+
}
|
|
3190
|
+
if (!connection) {
|
|
3191
|
+
interaction.failTurnSubmission(activeSessionId);
|
|
3192
|
+
addItem(systemItem("Wait for the server connection before sending a turn.", "warning"));
|
|
3193
|
+
return;
|
|
3194
|
+
}
|
|
3195
|
+
transcript = appendTranscriptItem(transcript, userItem(text));
|
|
3196
|
+
setNotice("running");
|
|
3197
|
+
render();
|
|
3198
|
+
const prompt = activeAgentId ? `@${activeAgentId} ${text}` : text;
|
|
3199
|
+
const modelId = activeModelId(options, payload?.providers);
|
|
3200
|
+
if (!modelId) {
|
|
3201
|
+
interaction.failTurnSubmission(activeSessionId);
|
|
3202
|
+
setNotice(null);
|
|
3203
|
+
addItem(systemItem(`No model selected for ${providerLabel(payload?.providers, options.provider)}. Use /model <id> or configure a default model in Desktop settings.`, "warning"));
|
|
3204
|
+
return;
|
|
3205
|
+
}
|
|
3206
|
+
try {
|
|
3207
|
+
await apiFetch(connection.server, connection.token, `/v1/sessions/${activeSessionId}/turns`, {
|
|
3208
|
+
method: "POST",
|
|
3209
|
+
body: JSON.stringify({
|
|
3210
|
+
prompt,
|
|
3211
|
+
...options.cwdExplicit || !options.project && !options.resume ? { cwd: options.cwd } : {},
|
|
3212
|
+
model: modelId,
|
|
3213
|
+
modelRef: activeModelRef(options, payload?.providers),
|
|
3214
|
+
approvalPolicy: "on-request",
|
|
3215
|
+
sandbox: "workspace-write"
|
|
3216
|
+
})
|
|
3217
|
+
});
|
|
3218
|
+
} catch (error) {
|
|
3219
|
+
interaction.failTurnSubmission(activeSessionId);
|
|
3220
|
+
setNotice(null);
|
|
3221
|
+
addItem(systemItem(error instanceof Error ? error.message : String(error), "error"));
|
|
3222
|
+
}
|
|
3223
|
+
}
|
|
3224
|
+
async function submitDirectCommand(command) {
|
|
3225
|
+
if (!connection || !activeSessionId) return;
|
|
3226
|
+
const latest = payload ?? await refreshBootstrap();
|
|
3227
|
+
const session = findTerminalSession(latest, activeSessionId);
|
|
3228
|
+
const blockedReason = terminalDirectCommandBlockedReason(session);
|
|
3229
|
+
if (blockedReason || !session) {
|
|
3230
|
+
addItem(systemItem(blockedReason ?? "Select a project to use this.", "warning"));
|
|
3231
|
+
return;
|
|
3232
|
+
}
|
|
3233
|
+
setNotice("running command");
|
|
3234
|
+
try {
|
|
3235
|
+
const result = await runTerminalDirectCommand(connection, session, command);
|
|
3236
|
+
for (const event of result.events) {
|
|
3237
|
+
transcript = appendRuntimeEvent(transcript, event);
|
|
3238
|
+
}
|
|
3239
|
+
payload = await refreshBootstrap().catch(() => payload);
|
|
3240
|
+
} catch (error) {
|
|
3241
|
+
addItem(systemItem(error instanceof Error ? error.message : String(error), "error"));
|
|
3242
|
+
} finally {
|
|
3243
|
+
setNotice(null);
|
|
3244
|
+
render();
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
async function handleSubmit(text) {
|
|
3248
|
+
try {
|
|
3249
|
+
const phase = interaction.snapshot().phase;
|
|
3250
|
+
if (phase === "starting" || phase === "switching" || phase === "stopping") {
|
|
3251
|
+
const command2 = parseSlashCommand(text);
|
|
3252
|
+
if (command2?.type === "exit") requestExit();
|
|
3253
|
+
else {
|
|
3254
|
+
const blocked = interaction.blocked("accepting input");
|
|
3255
|
+
if (!blocked.ok) addItem(systemItem(blocked.message, "warning"));
|
|
3256
|
+
}
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
if (permissionQuestion) {
|
|
3260
|
+
const choice = parseTerminalPermissionChoice(text);
|
|
3261
|
+
if (!choice) {
|
|
3262
|
+
addItem(systemItem("Choose yes, session, no, or skip.", "warning"));
|
|
3263
|
+
return;
|
|
3264
|
+
}
|
|
3265
|
+
transcript = appendTranscriptItem(transcript, userItem(text));
|
|
3266
|
+
render();
|
|
3267
|
+
await resolvePermissionQuestion(choice);
|
|
3268
|
+
return;
|
|
3269
|
+
}
|
|
3270
|
+
const directCommand = parseDirectCommandPrompt(text);
|
|
3271
|
+
if (directCommand) {
|
|
3272
|
+
const allowed = interaction.blocked("running a direct command");
|
|
3273
|
+
if (!allowed.ok) {
|
|
3274
|
+
addItem(systemItem(allowed.message, "warning"));
|
|
3275
|
+
return;
|
|
3276
|
+
}
|
|
3277
|
+
await commandScheduler.run(() => submitDirectCommand(directCommand.command));
|
|
3278
|
+
return;
|
|
3279
|
+
}
|
|
3280
|
+
const command = parseSlashCommand(text);
|
|
3281
|
+
if (command) {
|
|
3282
|
+
transcript = appendTranscriptItem(transcript, userItem(text));
|
|
3283
|
+
render();
|
|
3284
|
+
await commandScheduler.run(
|
|
3285
|
+
() => handleTerminalSlashCommand(command, {
|
|
3286
|
+
options,
|
|
3287
|
+
getConnection: () => connection,
|
|
3288
|
+
getPayload: () => payload,
|
|
3289
|
+
setPayload: (nextPayload) => {
|
|
3290
|
+
payload = nextPayload;
|
|
3291
|
+
},
|
|
3292
|
+
getActiveSessionId: () => activeSessionId,
|
|
3293
|
+
setActiveSessionId: (sessionId) => {
|
|
3294
|
+
activeSessionId = sessionId;
|
|
3295
|
+
},
|
|
3296
|
+
switchSession: async (create) => {
|
|
3297
|
+
const started = interaction.beginSessionSwitch();
|
|
3298
|
+
if (!started.ok) throw new Error(started.message);
|
|
3299
|
+
try {
|
|
3300
|
+
const created = await create();
|
|
3301
|
+
const session = await ensureTerminalSessionWorkspaceReady(connection, created) ?? created;
|
|
3302
|
+
activeSessionId = session.id;
|
|
3303
|
+
await eventStream?.switchSession(session.id);
|
|
3304
|
+
interaction.completeSessionSwitch(session.id);
|
|
3305
|
+
transcript = [];
|
|
3306
|
+
pendingCommandApproval = null;
|
|
3307
|
+
permissionQuestion = null;
|
|
3308
|
+
return session;
|
|
3309
|
+
} catch (error) {
|
|
3310
|
+
interaction.failSessionSwitch();
|
|
3311
|
+
throw error;
|
|
3312
|
+
}
|
|
3313
|
+
},
|
|
3314
|
+
getActiveAgentId: () => activeAgentId,
|
|
3315
|
+
setActiveAgentId: (agentId) => {
|
|
3316
|
+
activeAgentId = agentId;
|
|
3317
|
+
},
|
|
3318
|
+
getPendingCommandApproval: activePendingCommandApproval,
|
|
3319
|
+
openCommandApprovalQuestion,
|
|
3320
|
+
refreshBootstrap,
|
|
3321
|
+
addItem,
|
|
3322
|
+
clearTranscript: () => {
|
|
3323
|
+
transcript = [];
|
|
3324
|
+
},
|
|
3325
|
+
requestExit,
|
|
3326
|
+
render,
|
|
3327
|
+
openUrl: openUrlDetached
|
|
3328
|
+
})
|
|
3329
|
+
);
|
|
3330
|
+
return;
|
|
3331
|
+
}
|
|
3332
|
+
await submitPrompt(text);
|
|
3333
|
+
} catch (error) {
|
|
3334
|
+
addItem(systemItem(error instanceof Error ? error.message : String(error), "error"));
|
|
3335
|
+
}
|
|
3336
|
+
}
|
|
3337
|
+
function submitMenuCommand(command) {
|
|
3338
|
+
if (permissionQuestion) {
|
|
3339
|
+
const choice = parseTerminalPermissionChoice(composer.text) ?? parseTerminalPermissionChoice(command.submitText ?? command.name);
|
|
3340
|
+
composer = createComposer();
|
|
3341
|
+
render();
|
|
3342
|
+
if (choice) void resolvePermissionQuestion(choice);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
const commandText = command.submitText ?? `/${command.name}`;
|
|
3346
|
+
if (command.requiresArgument) {
|
|
3347
|
+
composer = replaceComposerText(composer, `${commandText} `);
|
|
3348
|
+
slashSelectedIndex = 0;
|
|
3349
|
+
render();
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
composer = {
|
|
3353
|
+
text: "",
|
|
3354
|
+
cursor: 0,
|
|
3355
|
+
history: [...composer.history.filter((item) => item !== commandText), commandText].slice(-100),
|
|
3356
|
+
historyIndex: null
|
|
3357
|
+
};
|
|
3358
|
+
slashSelectedIndex = 0;
|
|
3359
|
+
render();
|
|
3360
|
+
void handleSubmit(commandText);
|
|
3361
|
+
}
|
|
3362
|
+
async function handleKey(key) {
|
|
3363
|
+
if (key === "pageup") {
|
|
3364
|
+
scrollOffset += Math.max(4, Math.floor((output.rows || 24) / 2));
|
|
3365
|
+
render();
|
|
3366
|
+
return;
|
|
3367
|
+
}
|
|
3368
|
+
if (key === "pagedown") {
|
|
3369
|
+
scrollOffset = Math.max(0, scrollOffset - Math.max(4, Math.floor((output.rows || 24) / 2)));
|
|
3370
|
+
render();
|
|
3371
|
+
return;
|
|
3372
|
+
}
|
|
3373
|
+
const menu = activeSlashMenu();
|
|
3374
|
+
if (menu && key === "up") {
|
|
3375
|
+
if (permissionQuestion) permissionQuestionSelectedIndex = Math.max(0, permissionQuestionSelectedIndex - 1);
|
|
3376
|
+
else slashSelectedIndex = Math.max(0, slashSelectedIndex - 1);
|
|
3377
|
+
render();
|
|
3378
|
+
return;
|
|
3379
|
+
}
|
|
3380
|
+
if (menu && key === "down") {
|
|
3381
|
+
if (permissionQuestion) {
|
|
3382
|
+
permissionQuestionSelectedIndex = Math.min(menu.items.length - 1, permissionQuestionSelectedIndex + 1);
|
|
3383
|
+
} else {
|
|
3384
|
+
slashSelectedIndex = Math.min(menu.items.length - 1, slashSelectedIndex + 1);
|
|
3385
|
+
}
|
|
3386
|
+
render();
|
|
3387
|
+
return;
|
|
3388
|
+
}
|
|
3389
|
+
if (menu && key === "enter") {
|
|
3390
|
+
submitMenuCommand(menu.items[slashSelectedIndex] ?? menu.items[0]);
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
3393
|
+
const before = composer;
|
|
3394
|
+
const result = handleComposerKey(composer, key);
|
|
3395
|
+
composer = result.state;
|
|
3396
|
+
if (!activeSlashMenu()) slashSelectedIndex = 0;
|
|
3397
|
+
if (result.action.type === "submit") {
|
|
3398
|
+
render();
|
|
3399
|
+
void handleSubmit(result.action.text);
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
if (result.action.type === "cancel-or-exit") {
|
|
3403
|
+
if (permissionQuestion) {
|
|
3404
|
+
void resolvePermissionQuestion("skip");
|
|
3405
|
+
} else if (interaction.snapshot().phase === "running" && connection && activeSessionId) {
|
|
3406
|
+
setNotice("interrupting");
|
|
3407
|
+
void interruptTurn(connection.server, connection.token, activeSessionId).catch((error) => {
|
|
3408
|
+
addItem(systemItem(error instanceof Error ? error.message : String(error), "error"));
|
|
3409
|
+
});
|
|
3410
|
+
} else if (before.text) {
|
|
3411
|
+
composer = createComposer();
|
|
3412
|
+
render();
|
|
3413
|
+
} else {
|
|
3414
|
+
requestExit();
|
|
3415
|
+
}
|
|
3416
|
+
return;
|
|
3417
|
+
}
|
|
3418
|
+
if (result.action.type === "exit") {
|
|
3419
|
+
requestExit();
|
|
3420
|
+
return;
|
|
3421
|
+
}
|
|
3422
|
+
if (result.action.type === "rerender") renderer.fullRedraw();
|
|
3423
|
+
render();
|
|
3424
|
+
}
|
|
3425
|
+
function requestExit() {
|
|
3426
|
+
if (exitLatch.requested) return;
|
|
3427
|
+
interaction.beginStopping();
|
|
3428
|
+
exitLatch.request();
|
|
3429
|
+
}
|
|
3430
|
+
const rawInput = new RawInput(input, (key) => {
|
|
3431
|
+
void handleKey(key);
|
|
3432
|
+
});
|
|
3433
|
+
renderer.start();
|
|
3434
|
+
rawInput.start();
|
|
3435
|
+
try {
|
|
3436
|
+
const startupWarnings = [];
|
|
3437
|
+
connection = await ensureServer(options, (line) => startupWarnings.push(line));
|
|
3438
|
+
payload = await apiFetch(connection.server, connection.token, "/v1/bootstrap?refreshCodex=1");
|
|
3439
|
+
activeAgentId = payload.profile.agents[0]?.id ?? null;
|
|
3440
|
+
const sessionState = await ensureTerminalChatSession(connection, payload, options, activeSessionId);
|
|
3441
|
+
const readySession = await ensureTerminalSessionWorkspaceReady(connection, sessionState.session);
|
|
3442
|
+
activeSessionId = sessionState.sessionId;
|
|
3443
|
+
if (readySession?.cwd) options.cwd = readySession.cwd;
|
|
3444
|
+
options.provider = sessionState.provider;
|
|
3445
|
+
options.model = sessionState.model;
|
|
3446
|
+
notice = null;
|
|
3447
|
+
renderer.commitLines([...renderWelcome(status(), output.columns || 80), ""]);
|
|
3448
|
+
for (const line of startupWarnings) addItem(systemItem(line, "warning"));
|
|
3449
|
+
eventStream = await openTerminalEvents({
|
|
3450
|
+
server: connection.server,
|
|
3451
|
+
token: connection.token,
|
|
3452
|
+
activeSessionId: () => activeSessionId,
|
|
3453
|
+
onEvent: (event) => {
|
|
3454
|
+
interaction.applyRuntimeEvent(event);
|
|
3455
|
+
const requestedApproval = commandApprovalFromRuntimeEvent(event);
|
|
3456
|
+
if (requestedApproval) pendingCommandApproval = requestedApproval;
|
|
3457
|
+
const resolvedApprovalId = commandApprovalIdFromResolvedEvent(event);
|
|
3458
|
+
if (resolvedApprovalId && pendingCommandApproval?.id === resolvedApprovalId) {
|
|
3459
|
+
pendingCommandApproval = null;
|
|
3460
|
+
}
|
|
3461
|
+
if (resolvedApprovalId && permissionQuestion?.approval.id === resolvedApprovalId) {
|
|
3462
|
+
permissionQuestion = null;
|
|
3463
|
+
notice = null;
|
|
3464
|
+
}
|
|
3465
|
+
transcript = appendRuntimeEvent(transcript, event);
|
|
3466
|
+
if (event.name === "turn.completed" || event.name === "turn.failed" || event.name === "turn.interrupted") {
|
|
3467
|
+
notice = null;
|
|
3468
|
+
}
|
|
3469
|
+
eventRenderScheduler.request();
|
|
3470
|
+
},
|
|
3471
|
+
onStatus: (status2) => {
|
|
3472
|
+
if (status2.state === "disconnected") {
|
|
3473
|
+
eventStreamNotice = `event stream reconnecting in ${Math.ceil(status2.nextDelayMs / 1e3)}s`;
|
|
3474
|
+
eventRenderScheduler.request();
|
|
3475
|
+
} else if (status2.state === "connected" && eventStreamNotice) {
|
|
3476
|
+
eventStreamNotice = null;
|
|
3477
|
+
eventRenderScheduler.request();
|
|
3478
|
+
}
|
|
3479
|
+
}
|
|
3480
|
+
});
|
|
3481
|
+
await eventStream.ready;
|
|
3482
|
+
interaction.completeStartup(activeSessionId);
|
|
3483
|
+
render();
|
|
3484
|
+
await exitLatch.wait();
|
|
3485
|
+
} finally {
|
|
3486
|
+
eventStream?.abort();
|
|
3487
|
+
eventRenderScheduler.cancel();
|
|
3488
|
+
resizeRenderScheduler.cancel();
|
|
3489
|
+
rawInput.stop();
|
|
3490
|
+
renderer.stop();
|
|
3491
|
+
await stopManagedServer();
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
function printEvent(event) {
|
|
3495
|
+
if (event.name === "assistant.delta") output.write(event.output ?? "");
|
|
3496
|
+
else if (event.name === "turn.completed") output.write("\n[turn completed]\n");
|
|
3497
|
+
else if (event.name === "approval.requested") output.write(`
|
|
3498
|
+
[approval] ${event.output ?? event.action ?? "request"}
|
|
3499
|
+
`);
|
|
3500
|
+
else if (event.name === "command.output" && event.output) output.write(`
|
|
3501
|
+
[command] ${event.output}
|
|
3502
|
+
`);
|
|
3503
|
+
else if (event.name.startsWith("workspace_")) output.write(`
|
|
3504
|
+
[openpond] ${event.output ?? event.action ?? event.name}
|
|
3505
|
+
`);
|
|
3506
|
+
}
|
|
3507
|
+
async function lineModeChat(options) {
|
|
3508
|
+
const connection = await ensureServer(options, (line) => output.write(`${line}
|
|
3509
|
+
`));
|
|
3510
|
+
const payload = await apiFetch(connection.server, connection.token, "/v1/bootstrap?refreshCodex=1");
|
|
3511
|
+
const sessionState = await ensureTerminalChatSession(connection, payload, options, options.resume);
|
|
3512
|
+
const readySession = await ensureTerminalSessionWorkspaceReady(connection, sessionState.session);
|
|
3513
|
+
const activeSessionId = sessionState.sessionId;
|
|
3514
|
+
if (readySession?.cwd) options.cwd = readySession.cwd;
|
|
3515
|
+
options.provider = sessionState.provider;
|
|
3516
|
+
options.model = sessionState.model;
|
|
3517
|
+
const turnGuard = createLineModeTurnGuard();
|
|
3518
|
+
const eventStream = await openTerminalEvents({
|
|
3519
|
+
server: connection.server,
|
|
3520
|
+
token: connection.token,
|
|
3521
|
+
activeSessionId: () => activeSessionId,
|
|
3522
|
+
onEvent: (event) => {
|
|
3523
|
+
turnGuard.applyRuntimeEvent(event);
|
|
3524
|
+
printEvent(event);
|
|
3525
|
+
},
|
|
3526
|
+
onStatus: (status) => {
|
|
3527
|
+
if (status.state === "disconnected") {
|
|
3528
|
+
output.write(`
|
|
3529
|
+
[event stream] reconnecting in ${Math.ceil(status.nextDelayMs / 1e3)}s: ${status.message}
|
|
3530
|
+
`);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
});
|
|
3534
|
+
await eventStream.ready;
|
|
3535
|
+
const rl = createInterface({ input, output });
|
|
3536
|
+
output.write(`${terminalSessionHeading(payload.providers, options)}
|
|
3537
|
+
`);
|
|
3538
|
+
try {
|
|
3539
|
+
for await (const rawLine of rl) {
|
|
3540
|
+
const line = rawLine.trim();
|
|
3541
|
+
if (!line) continue;
|
|
3542
|
+
if (line === "/exit" || line === "/quit") break;
|
|
3543
|
+
if (!turnGuard.tryStartSubmission()) {
|
|
3544
|
+
output.write(`${LINE_MODE_TURN_RUNNING_MESSAGE}
|
|
3545
|
+
`);
|
|
3546
|
+
continue;
|
|
3547
|
+
}
|
|
3548
|
+
const modelId = activeModelId(options, payload.providers);
|
|
3549
|
+
if (!modelId) {
|
|
3550
|
+
turnGuard.failSubmission();
|
|
3551
|
+
throw new Error(`No model selected for ${providerLabel(payload.providers, options.provider)}.`);
|
|
3552
|
+
}
|
|
3553
|
+
try {
|
|
3554
|
+
await apiFetch(connection.server, connection.token, `/v1/sessions/${activeSessionId}/turns`, {
|
|
3555
|
+
method: "POST",
|
|
3556
|
+
body: JSON.stringify({
|
|
3557
|
+
prompt: line,
|
|
3558
|
+
...options.cwdExplicit || !options.project && !options.resume ? { cwd: options.cwd } : {},
|
|
3559
|
+
model: modelId,
|
|
3560
|
+
modelRef: activeModelRef(options, payload.providers),
|
|
3561
|
+
approvalPolicy: "on-request",
|
|
3562
|
+
sandbox: "workspace-write"
|
|
3563
|
+
})
|
|
3564
|
+
});
|
|
3565
|
+
} catch (error) {
|
|
3566
|
+
turnGuard.failSubmission();
|
|
3567
|
+
throw error;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
} finally {
|
|
3571
|
+
rl.close();
|
|
3572
|
+
eventStream.abort();
|
|
3573
|
+
await stopManagedServer();
|
|
3574
|
+
}
|
|
3575
|
+
}
|
|
3576
|
+
async function runOpenPondTerminalCli(argv = process.argv.slice(2)) {
|
|
3577
|
+
const { command, options } = parseTerminalArgs(argv);
|
|
3578
|
+
if (command !== "chat") {
|
|
3579
|
+
output.write(
|
|
3580
|
+
"Usage: openpond-app chat [--server URL] [--provider PROVIDER] [--model MODEL] [--cwd DIR] [--project APP_ID] [--resume SESSION_ID] (--message TEXT|--message-file PATH|--stdin) --non-interactive [--yes] [--approval-policy POLICY] [--json] [--timeout-sec SEC] [--max-output-bytes BYTES] [--sandbox MODE]\n"
|
|
3581
|
+
);
|
|
3582
|
+
return;
|
|
3583
|
+
}
|
|
3584
|
+
await chat(options);
|
|
3585
|
+
}
|
|
3586
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
3587
|
+
void runOpenPondTerminalCli().catch((error) => {
|
|
3588
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
3589
|
+
const exitCode = typeof error.exitCode === "number" ? error.exitCode : 1;
|
|
3590
|
+
process.exit(exitCode);
|
|
3591
|
+
});
|
|
3592
|
+
}
|
|
3593
|
+
export {
|
|
3594
|
+
runOpenPondTerminalCli
|
|
3595
|
+
};
|