hilos-agent 0.11.12 → 0.11.14
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/package.json +1 -1
- package/src/acp-session.mjs +1 -29
- package/src/agent-events.mjs +14 -17
- package/src/agent-result.mjs +56 -0
- package/src/child-mcp.mjs +2 -2
- package/src/claude-permissions.mjs +21 -42
- package/src/cli.mjs +2 -2
- package/src/codex-mcp-session.mjs +1 -10
- package/src/coding-transport.mjs +440 -0
- package/src/config.mjs +5 -7
- package/src/daemon.mjs +8 -13
- package/src/deploy.mjs +1 -1
- package/src/github-artifacts.mjs +49 -35
- package/src/handler.mjs +411 -1099
- package/src/hook.mjs +15 -47
- package/src/local-harness-events.mjs +0 -11
- package/src/mcp.mjs +17 -29
- package/src/opencode-permissions.mjs +2 -34
- package/src/opencode-session.mjs +1 -10
- package/src/permission-gate.mjs +1 -25
- package/src/progress-emitter.mjs +1 -6
- package/src/queue.mjs +1 -5
- package/src/reply-bridge.mjs +13 -11
- package/src/run.mjs +43 -114
- package/src/util.mjs +58 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hilos-agent",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.14",
|
|
4
4
|
"description": "Run your own coding agent (Claude Code, Codex, Cursor, OpenCode, Hermes, or any command) as a teammate in a hilos room. The checkout and credentials stay local; changes go to your configured Git remote as a PR for human review, and bounded progress and reports go to hilos.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/acp-session.mjs
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import { spawn } from "node:child_process";
|
|
20
20
|
import { createAcpEventMapper } from "./agent-events.mjs";
|
|
21
21
|
import { resolveHilosPermissionReply } from "./permission-gate.mjs";
|
|
22
|
+
import { abortError, isObject, raceWithAbort } from "./util.mjs";
|
|
22
23
|
|
|
23
24
|
const DEFAULT_TIMEOUT_MS = 30 * 60_000;
|
|
24
25
|
const DEFAULT_POLL_MS = 1_000;
|
|
@@ -26,35 +27,6 @@ const SHUTDOWN_GRACE_MS = 750;
|
|
|
26
27
|
const PROTOCOL_VERSION = 1;
|
|
27
28
|
const MAX_LINE_BYTES = 4 * 1024 * 1024;
|
|
28
29
|
|
|
29
|
-
function isObject(value) {
|
|
30
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function abortError(reason = "cancelled") {
|
|
34
|
-
const error = new Error(String(reason || "cancelled"));
|
|
35
|
-
error.name = "AbortError";
|
|
36
|
-
return error;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function raceWithAbort(promise, signal) {
|
|
40
|
-
if (!signal) return promise;
|
|
41
|
-
if (signal.aborted) return Promise.reject(abortError(signal.reason));
|
|
42
|
-
return new Promise((resolve, reject) => {
|
|
43
|
-
const onAbort = () => reject(abortError(signal.reason));
|
|
44
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
45
|
-
Promise.resolve(promise).then(
|
|
46
|
-
(value) => {
|
|
47
|
-
signal.removeEventListener("abort", onAbort);
|
|
48
|
-
resolve(value);
|
|
49
|
-
},
|
|
50
|
-
(error) => {
|
|
51
|
-
signal.removeEventListener("abort", onAbort);
|
|
52
|
-
reject(error);
|
|
53
|
-
},
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
30
|
/**
|
|
59
31
|
* Choose the agent's option id for a hilos reply ("once"|"always"|"reject").
|
|
60
32
|
* Matches on the ACP option `kind` first (allow_once / allow_always /
|
package/src/agent-events.mjs
CHANGED
|
@@ -16,16 +16,16 @@
|
|
|
16
16
|
// stripped, length capped) — a chatty CLI can echo a `Bearer …` header.
|
|
17
17
|
//
|
|
18
18
|
// Claude shapes here MATCH `lib/hosted-worklog.ts` / `lib/hosted-agent.ts`
|
|
19
|
-
// (`formatClaudeEvent`, `parseStreamResult`) so
|
|
20
|
-
//
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
// (`formatClaudeEvent`, `parseStreamResult`) so the daemon and the hosted path
|
|
20
|
+
// render the same run from the same shapes (0274).
|
|
21
|
+
|
|
22
|
+
import { COMMAND_LABEL_MAX, truncate } from "./util.mjs";
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @typedef {object} AgentEvent
|
|
26
26
|
* A normalized, render-ready step. Exactly one of these per meaningful thing the
|
|
27
27
|
* coding agent did. Small on purpose — the LiveRunCard maps `t` to a label.
|
|
28
|
-
* @property {'
|
|
28
|
+
* @property {'edit'|'run'|'read'|'think'|'note'|'session'|'result'|'usage'|'websearch'|'webfetch'|'subagent'} t
|
|
29
29
|
* - 'session' → { sessionId } the CLI's resumable session id (init only)
|
|
30
30
|
* - 'edit' → { path } wrote/edited a file
|
|
31
31
|
* - 'read' → { path } read a file
|
|
@@ -44,9 +44,6 @@
|
|
|
44
44
|
* reported it (0787). Never a step — the
|
|
45
45
|
* ring and the activity fold skip it; it
|
|
46
46
|
* rides home on the report instead.
|
|
47
|
-
* - 'phase' → { name } reserved lifecycle marker (unused by v1
|
|
48
|
-
* parsers; kept so 0274 can add start/end
|
|
49
|
-
* phases without widening the type)
|
|
50
47
|
* @property {string} [sessionId]
|
|
51
48
|
* @property {string} [path]
|
|
52
49
|
* @property {string} [cmd]
|
|
@@ -95,7 +92,7 @@ export function sanitizeText(value) {
|
|
|
95
92
|
if (typeof value !== "string") return "";
|
|
96
93
|
let out = value;
|
|
97
94
|
for (const re of SECRET_PATTERNS) out = out.replace(re, REDACTED);
|
|
98
|
-
|
|
95
|
+
out = truncate(out, MAX_LEN);
|
|
99
96
|
return out;
|
|
100
97
|
}
|
|
101
98
|
|
|
@@ -269,7 +266,7 @@ function webPhrase(value) {
|
|
|
269
266
|
// would render.
|
|
270
267
|
const clean = sanitizeText(raw);
|
|
271
268
|
if (!clean) return "";
|
|
272
|
-
return clean
|
|
269
|
+
return truncate(clean, WEB_LABEL_MAX);
|
|
273
270
|
}
|
|
274
271
|
|
|
275
272
|
/**
|
|
@@ -879,7 +876,7 @@ function normalizeVendor(vendor) {
|
|
|
879
876
|
// --- Streaming parsers -----------------------------------------------------
|
|
880
877
|
|
|
881
878
|
/**
|
|
882
|
-
* The daemon pushes raw stdout chunks (
|
|
879
|
+
* The daemon pushes raw stdout chunks (handler.mjs `handleCliData`) that can
|
|
883
880
|
* split a JSON object mid-line. A structured parser buffers the partial tail and
|
|
884
881
|
* only parses COMPLETE lines; flush() drains a trailing unterminated line.
|
|
885
882
|
* @param {(line: string) => AgentEvent[]} parseLine
|
|
@@ -913,7 +910,7 @@ function makeLineBufferedParser(parseLine) {
|
|
|
913
910
|
* Fallback for any vendor with no structured stream (cursor graduated to a
|
|
914
911
|
* real parser in 0573). There's nothing to parse, so we only remember the last
|
|
915
912
|
* non-empty line seen — matching exactly what the daemon does today
|
|
916
|
-
* (handler.mjs
|
|
913
|
+
* (handler.mjs `handleCliData`) so we NEVER regress below it — and emit at most one
|
|
917
914
|
* sparse note on flush.
|
|
918
915
|
*/
|
|
919
916
|
function makeTextTailParser() {
|
|
@@ -964,13 +961,13 @@ function describeRun(cmd) {
|
|
|
964
961
|
const c = String(cmd || "").trim();
|
|
965
962
|
if (!c) return "Running a command";
|
|
966
963
|
if (/\b(test|tests)\b|vitest|jest|pytest|\bgo test\b/i.test(c)) return "Running tests";
|
|
967
|
-
const short = c
|
|
964
|
+
const short = truncate(c, COMMAND_LABEL_MAX);
|
|
968
965
|
return `Running ${short}`;
|
|
969
966
|
}
|
|
970
967
|
|
|
971
968
|
/**
|
|
972
969
|
* One AgentEvent → a human step label, or null for events that aren't shown as
|
|
973
|
-
* steps (session
|
|
970
|
+
* steps (session events are metadata). Pure.
|
|
974
971
|
* @param {AgentEvent} event
|
|
975
972
|
* @returns {string | null}
|
|
976
973
|
*/
|
|
@@ -993,7 +990,7 @@ export function stepLabel(event) {
|
|
|
993
990
|
case "result":
|
|
994
991
|
return event.ok ? "Done" : "Finished with errors";
|
|
995
992
|
default:
|
|
996
|
-
return null; // session /
|
|
993
|
+
return null; // session / unknown → not a step
|
|
997
994
|
}
|
|
998
995
|
}
|
|
999
996
|
|
|
@@ -1081,7 +1078,7 @@ function activitySentence(row) {
|
|
|
1081
1078
|
* truth a CLI stream carries, so "done" is the sequential-execution inference:
|
|
1082
1079
|
* a new action starting settles the one in flight. `settle("failed")` is for a
|
|
1083
1080
|
* dying run — leaving "Editing…" forever is the dishonesty this feed exists to
|
|
1084
|
-
* avoid. session
|
|
1081
|
+
* avoid. session events are metadata, not rows (same as stepLabel).
|
|
1085
1082
|
* @param {number} [limit]
|
|
1086
1083
|
*/
|
|
1087
1084
|
export function createActivityFold(limit = MAX_ACTIVITY) {
|
|
@@ -1151,7 +1148,7 @@ export function createActivityFold(limit = MAX_ACTIVITY) {
|
|
|
1151
1148
|
});
|
|
1152
1149
|
return;
|
|
1153
1150
|
default:
|
|
1154
|
-
return; // session /
|
|
1151
|
+
return; // session / unknown — metadata, not activity
|
|
1155
1152
|
}
|
|
1156
1153
|
}
|
|
1157
1154
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 1298 — one writing contract and final-result envelope for hosted and local runs.
|
|
2
|
+
export const AGENT_REPLY_STYLE_RULE = [
|
|
3
|
+
"How to write the reply:",
|
|
4
|
+
"- Lead with the answer or concrete outcome, in words a teammate outside the code can use. Never restate the request.",
|
|
5
|
+
"- Default to 1-3 short sentences, or at most three short bullets for separate outcomes.",
|
|
6
|
+
"- For instructions, put the action or command first and number sequential steps. For choices, recommend one first, then give the requested alternatives with short trade-offs.",
|
|
7
|
+
"- Include useful links once. Omit facts that do not change the answer or next decision: routine checks, file counts, and process history. No preamble, headings on short replies, sign-off, recap, or tangents.",
|
|
8
|
+
"- Keep blockers, material limitations, and real uncertainty visible. State errors plainly. Never invent causes, time estimates, checks, deployment, or merging.",
|
|
9
|
+
"- When blocked on a person, ask for the one missing decision: 'Which inbox should receive the form?' Otherwise, do not add a question. Do authorized work yourself; do not hand it back or ask permission again. When done, stop; no follow-up offers.",
|
|
10
|
+
"- Give requested documents, plans, code, research, and explanations the detail they need; keep their introduction short. Put code and commands in fenced blocks.",
|
|
11
|
+
"The room's voice may shape tone, but only a person's request for detail expands this default. Clarity beats a word limit.",
|
|
12
|
+
].join("\n");
|
|
13
|
+
|
|
14
|
+
export const AGENT_CODE_RESULT_RULE = [
|
|
15
|
+
AGENT_REPLY_STYLE_RULE,
|
|
16
|
+
"When the coding turn finishes, return one fenced hilos-result JSON object with three string fields:",
|
|
17
|
+
'{"summary":"The short result for the teammate.","prTitle":"A short title describing the actual problem fixed","prDescription":"The problem, resulting behavior, and relevant verification."}',
|
|
18
|
+
"Write summary for someone who may never open the code. Do not repeat the PR title, request, branch, file counts, or tool history. Include material limitations.",
|
|
19
|
+
"Write prTitle and prDescription as the pull request itself for a reviewer opening it cold. Title: under 60 characters, no trailing period or request echo. Description: usually 1-2 short paragraphs; only actual checks. No transcript, preamble, boilerplate, or attribution footer; hilos adds credit.",
|
|
20
|
+
"If you create or update the PR yourself, use those same title and description fields on GitHub. Describe the entire final PR change, including prior work on an iteration. Preserve text a person edited.",
|
|
21
|
+
"Return empty PR fields when there is no code change. A VISUAL_PREVIEW: /route line may follow the fence.",
|
|
22
|
+
].join("\n");
|
|
23
|
+
|
|
24
|
+
/** Keep complete artifacts, never half a sentence, Markdown link, or JSON object. */
|
|
25
|
+
export function retainAgentResult(value) {
|
|
26
|
+
return typeof value === "string" && value.length <= 32_000 ? value.trim() : "";
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** @returns {{ summary: string, prTitle: string, prDescription: string } | null} */
|
|
30
|
+
export function readAgentResult(value) {
|
|
31
|
+
const text = retainAgentResult(value);
|
|
32
|
+
const fenced = /```hilos-result\s*\n([\s\S]*?)\n```/.exec(text);
|
|
33
|
+
const candidate = fenced?.[1] ?? (text.startsWith("{") ? text : null);
|
|
34
|
+
if (!candidate) return null;
|
|
35
|
+
try {
|
|
36
|
+
const result = JSON.parse(candidate);
|
|
37
|
+
if (!result || typeof result !== "object" || typeof result.summary !== "string" ||
|
|
38
|
+
typeof result.prTitle !== "string" || typeof result.prDescription !== "string") return null;
|
|
39
|
+
return {
|
|
40
|
+
summary: result.summary.trim(),
|
|
41
|
+
prTitle: result.prTitle.trim(),
|
|
42
|
+
prDescription: result.prDescription.trim(),
|
|
43
|
+
};
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Legacy prose remains readable; an invalid machine envelope never enters chat. */
|
|
50
|
+
export function agentResultText(value) {
|
|
51
|
+
const text = retainAgentResult(value);
|
|
52
|
+
const result = readAgentResult(text);
|
|
53
|
+
if (result) return result.summary;
|
|
54
|
+
if (text.includes("```hilos-result") || /^\s*\{\s*"(?:summary|prTitle|prDescription)"/.test(text)) return "";
|
|
55
|
+
return text.replace(/^\s*VISUAL_PREVIEW:.*$/gm, "").trim();
|
|
56
|
+
}
|
package/src/child-mcp.mjs
CHANGED
|
@@ -147,7 +147,7 @@ export function childMcpPlanned({ cfg, vendor, bindingClaim, env } = {}) {
|
|
|
147
147
|
}
|
|
148
148
|
|
|
149
149
|
/** The `mcpServers` fragment for a claude `--mcp-config` JSON. */
|
|
150
|
-
|
|
150
|
+
function childMcpServers(url) {
|
|
151
151
|
return { [CHILD_MCP_SERVER]: { type: "http", url } };
|
|
152
152
|
}
|
|
153
153
|
|
|
@@ -159,7 +159,7 @@ export function childMcpServers(url) {
|
|
|
159
159
|
* config file instead — claude takes `--mcp-config` more than once, but one
|
|
160
160
|
* file the run owns end-to-end is one file to delete.
|
|
161
161
|
*/
|
|
162
|
-
|
|
162
|
+
function writeChildMcpConfig(url, { dir = os.tmpdir() } = {}) {
|
|
163
163
|
const configPath = path.join(
|
|
164
164
|
dir,
|
|
165
165
|
`hilos-child-mcp-${process.pid}-${randomBytes(6).toString("hex")}.json`,
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
DEFAULT_PERMISSION_TIMEOUT_MS,
|
|
39
39
|
resolveHilosPermissionReply,
|
|
40
40
|
} from "./permission-gate.mjs";
|
|
41
|
+
import { isObject } from "./util.mjs";
|
|
41
42
|
|
|
42
43
|
/** The MCP server key and tool name the daemon serves. Together they form the
|
|
43
44
|
* `mcp__<server>__<tool>` identifier claude's flag takes. */
|
|
@@ -54,10 +55,6 @@ const DEFAULT_SESSION_ID_WAIT_MS = 10_000;
|
|
|
54
55
|
/** Tool names whose input names a file rather than a command. */
|
|
55
56
|
const FILE_INPUT_KEYS = ["file_path", "path", "notebook_path"];
|
|
56
57
|
|
|
57
|
-
function isObject(value) {
|
|
58
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
58
|
/**
|
|
62
59
|
* Shape claude's permission-prompt payload into the SAME vendor-neutral request
|
|
63
60
|
* the opencode/ACP card flow already produces, so the card, its audit row, and
|
|
@@ -184,9 +181,6 @@ export function claudePermissionArgs({
|
|
|
184
181
|
];
|
|
185
182
|
}
|
|
186
183
|
|
|
187
|
-
/** The flags to strip when a CLI turns out not to know them (compat retry). */
|
|
188
|
-
export const CLAUDE_PERMISSION_FLAGS = ["--mcp-config", "--permission-prompt-tool"];
|
|
189
|
-
|
|
190
184
|
/**
|
|
191
185
|
* Serve the permission tool on loopback for the life of one run.
|
|
192
186
|
*
|
|
@@ -209,8 +203,7 @@ export const CLAUDE_PERMISSION_FLAGS = ["--mcp-config", "--permission-prompt-too
|
|
|
209
203
|
* allowedTools?: string,
|
|
210
204
|
* }} options
|
|
211
205
|
* @returns {Promise<{ configPath: string, toolId: string, url: string, port: number,
|
|
212
|
-
* args: string[],
|
|
213
|
-
* close: () => Promise<void> }>}
|
|
206
|
+
* args: string[], close: () => Promise<void> }>}
|
|
214
207
|
*/
|
|
215
208
|
export async function startClaudePermissionServer({
|
|
216
209
|
requestPermission,
|
|
@@ -235,7 +228,6 @@ export async function startClaudePermissionServer({
|
|
|
235
228
|
}
|
|
236
229
|
const token = randomBytes(24).toString("hex");
|
|
237
230
|
let seq = 0;
|
|
238
|
-
let inflight = 0;
|
|
239
231
|
let currentSessionId = String(sessionId || "");
|
|
240
232
|
// hilos's server REJECTS a permission request with an empty vendorSessionId,
|
|
241
233
|
// so a fresh run (no session to resume) must not ask with one — every card
|
|
@@ -373,33 +365,25 @@ export async function startClaudePermissionServer({
|
|
|
373
365
|
sessionId: await sessionIdForAsk(),
|
|
374
366
|
fallbackId: String(++seq),
|
|
375
367
|
});
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
});
|
|
396
|
-
decisionReply = resolved.reply;
|
|
397
|
-
outcome = resolved.outcome;
|
|
398
|
-
byPolicy = resolved.byPolicy === true;
|
|
399
|
-
policyReason = resolved.policyReason ?? null;
|
|
400
|
-
} finally {
|
|
401
|
-
inflight--;
|
|
402
|
-
}
|
|
368
|
+
const resolved = await resolveHilosPermissionReply({
|
|
369
|
+
request,
|
|
370
|
+
requestPermission,
|
|
371
|
+
getPermissionDecision,
|
|
372
|
+
signal,
|
|
373
|
+
timeoutMs,
|
|
374
|
+
pollIntervalMs,
|
|
375
|
+
log,
|
|
376
|
+
label: "claude permission",
|
|
377
|
+
localAllow,
|
|
378
|
+
});
|
|
379
|
+
const decisionReply = resolved.reply;
|
|
380
|
+
const outcome = resolved.outcome;
|
|
381
|
+
// byPolicy is set only when a workspace RULE refused this ask (0813); a
|
|
382
|
+
// person's rejection leaves it false and keeps the human wording.
|
|
383
|
+
// Provenance is tracked apart from the reason because a rule may carry no
|
|
384
|
+
// reason.
|
|
385
|
+
const byPolicy = resolved.byPolicy === true;
|
|
386
|
+
const policyReason = resolved.policyReason ?? null;
|
|
403
387
|
// A rejection is never an MCP error: an isError result makes claude retry
|
|
404
388
|
// or improvise around the block. A plain deny result is the refusal the
|
|
405
389
|
// CLI is built to respect.
|
|
@@ -452,11 +436,6 @@ export async function startClaudePermissionServer({
|
|
|
452
436
|
url,
|
|
453
437
|
port,
|
|
454
438
|
args: claudePermissionArgs({ configPath, allowedTools }),
|
|
455
|
-
/** Let the caller stamp the CLI's real session id onto later cards. */
|
|
456
|
-
setSessionId(value) {
|
|
457
|
-
if (typeof value === "string" && value) currentSessionId = value;
|
|
458
|
-
},
|
|
459
|
-
pending: () => inflight,
|
|
460
439
|
async close() {
|
|
461
440
|
try {
|
|
462
441
|
fs.unlinkSync(configPath);
|
package/src/cli.mjs
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// logs a periodic "still working…" heartbeat so the run visibly stays alive.
|
|
7
7
|
|
|
8
8
|
import { spawn } from "node:child_process";
|
|
9
|
+
import { truncate } from "./util.mjs";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Let the daemon finish its existing abort/cleanup path before exiting.
|
|
@@ -150,8 +151,7 @@ export function fmtElapsed(ms) {
|
|
|
150
151
|
|
|
151
152
|
/** Collapse to one trimmed, length-capped line (for a heartbeat's "Latest:" tail). */
|
|
152
153
|
export function oneLine(s, max = 140) {
|
|
153
|
-
|
|
154
|
-
return t.length > max ? t.slice(0, max - 1) + "…" : t;
|
|
154
|
+
return truncate(String(s || "").replace(/\s+/g, " ").trim(), max);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
/**
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
DEFAULT_PERMISSION_TIMEOUT_MS,
|
|
59
59
|
resolveHilosPermissionReply,
|
|
60
60
|
} from "./permission-gate.mjs";
|
|
61
|
+
import { isObject } from "./util.mjs";
|
|
61
62
|
|
|
62
63
|
// Codex 0.153.4 accepts on-request/never on its MCP tool. `untrusted`
|
|
63
64
|
// is rejected before a turn starts. Keep the workspace sandbox and route its
|
|
@@ -65,16 +66,6 @@ import {
|
|
|
65
66
|
const DEFAULT_TIMEOUT_MS = 30 * 60_000;
|
|
66
67
|
const SHUTDOWN_GRACE_MS = 750;
|
|
67
68
|
|
|
68
|
-
function isObject(value) {
|
|
69
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function abortError(reason = "cancelled") {
|
|
73
|
-
const error = new Error(String(reason || "cancelled"));
|
|
74
|
-
error.name = "AbortError";
|
|
75
|
-
return error;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
69
|
/**
|
|
79
70
|
* Render codex's argv-array command as the one line a human decides about.
|
|
80
71
|
* Codex wraps shell work as ["/bin/zsh","-lc","<the real command>"]; showing
|