borgmcp 5.2.0 → 5.4.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/dist/agent-runtime.d.ts +3 -4
- package/dist/agent-runtime.d.ts.map +1 -1
- package/dist/agent-runtime.js +9 -5
- package/dist/agent-runtime.js.map +1 -1
- package/dist/agent-session-identity.d.ts +13 -0
- package/dist/agent-session-identity.d.ts.map +1 -0
- package/dist/agent-session-identity.js +106 -0
- package/dist/agent-session-identity.js.map +1 -0
- package/dist/assimilate-cmd.d.ts +1 -11
- package/dist/assimilate-cmd.d.ts.map +1 -1
- package/dist/assimilate-cmd.js +37 -48
- package/dist/assimilate-cmd.js.map +1 -1
- package/dist/assimilate-deps.d.ts.map +1 -1
- package/dist/assimilate-deps.js +1 -4
- package/dist/assimilate-deps.js.map +1 -1
- package/dist/claude.d.ts.map +1 -1
- package/dist/claude.js +36 -77
- package/dist/claude.js.map +1 -1
- package/dist/codex-app-server.d.ts +4 -6
- package/dist/codex-app-server.d.ts.map +1 -1
- package/dist/codex-app-server.js +4 -25
- package/dist/codex-app-server.js.map +1 -1
- package/dist/codex-app-wake.d.ts +8 -10
- package/dist/codex-app-wake.d.ts.map +1 -1
- package/dist/codex-app-wake.js +49 -66
- package/dist/codex-app-wake.js.map +1 -1
- package/dist/codex-launch.d.ts +15 -24
- package/dist/codex-launch.d.ts.map +1 -1
- package/dist/codex-launch.js +29 -55
- package/dist/codex-launch.js.map +1 -1
- package/dist/codex-remote.d.ts +15 -8
- package/dist/codex-remote.d.ts.map +1 -1
- package/dist/codex-remote.js +71 -70
- package/dist/codex-remote.js.map +1 -1
- package/dist/codex-wake-resolve.d.ts +12 -31
- package/dist/codex-wake-resolve.d.ts.map +1 -1
- package/dist/codex-wake-resolve.js +21 -38
- package/dist/codex-wake-resolve.js.map +1 -1
- package/dist/config-utils.js +2 -2
- package/dist/config-utils.js.map +1 -1
- package/dist/cubes.d.ts +0 -17
- package/dist/cubes.d.ts.map +1 -1
- package/dist/cubes.js +0 -81
- package/dist/cubes.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -5
- package/dist/index.js.map +1 -1
- package/dist/lifecycle-log-guard.d.ts +6 -8
- package/dist/lifecycle-log-guard.d.ts.map +1 -1
- package/dist/lifecycle-log-guard.js +74 -22
- package/dist/lifecycle-log-guard.js.map +1 -1
- package/dist/log-stream.d.ts.map +1 -1
- package/dist/log-stream.js +4 -1
- package/dist/log-stream.js.map +1 -1
- package/dist/opencode-drone.d.ts +2 -0
- package/dist/opencode-drone.d.ts.map +1 -1
- package/dist/opencode-drone.js +16 -0
- package/dist/opencode-drone.js.map +1 -1
- package/dist/regen-format.js +1 -1
- package/dist/regen-format.js.map +1 -1
- package/dist/regen.js +11 -1
- package/dist/regen.js.map +1 -1
- package/docs/SESSION_IDENTITY.md +15 -0
- package/package.json +1 -1
- package/src/agent-runtime.ts +9 -5
- package/src/agent-session-identity.ts +98 -0
- package/src/assimilate-cmd.ts +35 -58
- package/src/assimilate-deps.ts +0 -4
- package/src/claude.ts +34 -81
- package/src/codex-app-server.ts +8 -25
- package/src/codex-app-wake.ts +46 -73
- package/src/codex-launch.ts +43 -76
- package/src/codex-remote.ts +90 -82
- package/src/codex-wake-resolve.ts +26 -47
- package/src/config-utils.ts +2 -2
- package/src/cubes.ts +2 -100
- package/src/index.ts +10 -5
- package/src/lifecycle-log-guard.ts +70 -26
- package/src/log-stream.ts +4 -1
- package/src/opencode-drone.ts +14 -0
- package/src/regen-format.ts +1 -1
- package/src/regen.ts +7 -1
package/dist/regen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regen.js","sourceRoot":"","sources":["../src/regen.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EACL,eAAe,EACf,qBAAqB,EACrB,0BAA0B,EAC1B,+BAA+B,GAChC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,EAAE,iCAAiC,EAAkB,MAAM,oBAAoB,CAAC;AACvF,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EACL,iCAAiC,EACjC,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,6BAA6B,EAC7B,qBAAqB,EACrB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B;;;;;GAKG;AACH,KAAK,UAAU,SAAS;IACtB,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK;YAAE,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;QACtE,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,iBAAiB,EAAE,CAAC;IACpB,MAAM,
|
|
1
|
+
{"version":3,"file":"regen.js","sourceRoot":"","sources":["../src/regen.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Agent Session Identity
|
|
2
|
+
|
|
3
|
+
ARRIVAL is announced once for each known agent session and cube/drone pair, using local lifecycle state. Changing the ARRIVAL wording or reconnecting the MCP transport does not create another announcement for that identity. A different session announces immediately, without a time window. Resuming an already announced session remains suppressed. This state does not affect credentials, authorization, stream ownership, or liveness.
|
|
4
|
+
|
|
5
|
+
The startup instruction itself remains process-local: reconnecting may display it again. Calling `borg_log` with ARRIVAL applies the persistent identity check. Unknown identity always announces rather than concealing a possible restart. READY suppression is unchanged.
|
|
6
|
+
|
|
7
|
+
## Harness Coverage
|
|
8
|
+
|
|
9
|
+
- **Claude Code:** the latest SessionStart hook observed for this launch. Borg puts a random correlation in the launch environment. `borg-regen` stores that correlation, the hook's `session_id`, and its observation time in the worktree's `.borgmcp/claude-session.json`. A reconnect with the same correlation reads the same observation; a successful hook on resume replaces it. Missing, invalid, unreadable, or prior-launch state is unknown. Claude identity can be stale after a resume whose SessionStart hook failed.
|
|
10
|
+
- **Codex:** exactly one non-ephemeral user thread, excluding subagents, in this launch's Borg-owned app-server. Zero or multiple candidates, an unreadable thread, or an unavailable socket is unknown. No recency guess identifies a session.
|
|
11
|
+
- **OpenCode:** the existing launch-correlation binding, rechecked against session metadata. Missing or changed binding, an unseen root session, duplicated launch metadata, or failed inspection is unknown. The identity reader does not follow the wake injector's `/new` selection heuristic.
|
|
12
|
+
|
|
13
|
+
`borg_stream-status` reports the identity source, the harness-prefixed session id, and the age of the observation in its text response. For Claude this age is measured from the hook, not the MCP child's start. Age is diagnostic only: there is no expiry heuristic. For Codex and OpenCode the observation is a fresh query. Unknown results report their reason.
|
|
14
|
+
|
|
15
|
+
These identifiers denote harness conversations, not operating-system processes. Restarting a harness into a new conversation produces a new identity; explicitly resuming the same conversation retains its identity when ownership is known. Session identifiers are not secrets or authorization inputs.
|
package/package.json
CHANGED
package/src/agent-runtime.ts
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { BORG_STATE_ROOT_ENV } from './private-root.js';
|
|
9
|
+
import { randomBytes } from 'node:crypto';
|
|
9
10
|
|
|
10
11
|
export type AgentKind = 'claude' | 'codex' | 'opencode';
|
|
11
12
|
|
|
12
13
|
/** Pinned into MCP-child environments by Borg launch paths. */
|
|
13
14
|
export const BORG_AGENT_KIND_ENV = 'BORG_AGENT_KIND';
|
|
15
|
+
export const BORG_CLAUDE_LAUNCH_CORRELATION_ENV = 'BORG_CLAUDE_LAUNCH_CORRELATION';
|
|
14
16
|
/** Transport capability only — never use it as the primary CLI identity. */
|
|
15
17
|
export const BORG_CODEX_REMOTE_WAKE_ENV = 'BORG_CODEX_REMOTE_WAKE';
|
|
16
18
|
/** Legacy OpenCode runtime marker, retained for installed-config compatibility. */
|
|
@@ -52,7 +54,11 @@ export function withAgentRuntimeEnv(
|
|
|
52
54
|
delete next[BORG_AGENT_KIND_ENV];
|
|
53
55
|
delete next[BORG_CODEX_REMOTE_WAKE_ENV];
|
|
54
56
|
delete next[BORG_OPENCODE_ENV];
|
|
57
|
+
delete next[BORG_CLAUDE_LAUNCH_CORRELATION_ENV];
|
|
55
58
|
next[BORG_AGENT_KIND_ENV] = agentKind;
|
|
59
|
+
if (agentKind === 'claude') {
|
|
60
|
+
next[BORG_CLAUDE_LAUNCH_CORRELATION_ENV] = randomBytes(32).toString('base64url');
|
|
61
|
+
}
|
|
56
62
|
if (agentKind === 'opencode') next[BORG_OPENCODE_ENV] = '1';
|
|
57
63
|
return next;
|
|
58
64
|
}
|
|
@@ -66,14 +72,12 @@ export function codexAgentKindConfigArgs(): string[] {
|
|
|
66
72
|
* Pin the remote-wake transport capability separately from the CLI identity.
|
|
67
73
|
*
|
|
68
74
|
* Codex MCP children read their configured env rather than the wrapper's
|
|
69
|
-
* inherited process env.
|
|
70
|
-
* therefore necessary to override legacy static configs that used to persist
|
|
71
|
-
* BORG_CODEX_REMOTE_WAKE="1" as an identity marker.
|
|
75
|
+
* inherited process env. Every Borg Codex launch owns a remote socket.
|
|
72
76
|
*/
|
|
73
|
-
export function codexRemoteWakeConfigArgs(
|
|
77
|
+
export function codexRemoteWakeConfigArgs(): string[] {
|
|
74
78
|
return [
|
|
75
79
|
'-c',
|
|
76
|
-
`mcp_servers.borg.env.${BORG_CODEX_REMOTE_WAKE_ENV}="
|
|
80
|
+
`mcp_servers.borg.env.${BORG_CODEX_REMOTE_WAKE_ENV}="1"`,
|
|
77
81
|
];
|
|
78
82
|
}
|
|
79
83
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { readFile, rename, unlink, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { BORG_CLAUDE_LAUNCH_CORRELATION_ENV, resolveReportableSessionAgentKind } from './agent-runtime.js';
|
|
5
|
+
import { findProjectRoot } from './cubes.js';
|
|
6
|
+
import { ensurePrivateBorgConfigRoot } from './private-root.js';
|
|
7
|
+
import { CodexAppServerClient } from './codex-app-server.js';
|
|
8
|
+
import { codexAppServerSocketFromEnv, isCodexSubagentSource } from './codex-wake-resolve.js';
|
|
9
|
+
import { resolveOpenCodeAgentSessionId } from './opencode-drone.js';
|
|
10
|
+
|
|
11
|
+
export type AgentSessionIdentity =
|
|
12
|
+
| { kind: 'known'; id: string; source: string; observedAt: string }
|
|
13
|
+
| { kind: 'unknown'; reason: string };
|
|
14
|
+
|
|
15
|
+
function validId(value: unknown): value is string {
|
|
16
|
+
return typeof value === 'string' && value.length > 0 && value.length <= 512 && !/[\s\x00-\x1f\x7f]/.test(value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function claudeCorrelation(env: NodeJS.ProcessEnv): string | null {
|
|
20
|
+
const value = env[BORG_CLAUDE_LAUNCH_CORRELATION_ENV];
|
|
21
|
+
if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value)) return null;
|
|
22
|
+
return Buffer.from(value, 'base64url').toString('base64url') === value ? value : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Identity only: no credential, authorization, liveness, or expiry semantics. */
|
|
26
|
+
export async function recordClaudeSessionStart(
|
|
27
|
+
payload: string,
|
|
28
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
29
|
+
worktree = findProjectRoot(),
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
const correlation = claudeCorrelation(env);
|
|
32
|
+
if (!correlation) return;
|
|
33
|
+
let sessionId: unknown;
|
|
34
|
+
try { sessionId = JSON.parse(payload)?.session_id; } catch { /* Persist unknown rather than retain an old id. */ }
|
|
35
|
+
const root = join(worktree, '.borgmcp');
|
|
36
|
+
await ensurePrivateBorgConfigRoot(root);
|
|
37
|
+
try { await writeFile(join(root, '.gitignore'), '*\n', { flag: 'wx', mode: 0o600 }); }
|
|
38
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error; }
|
|
39
|
+
const path = join(root, 'claude-session.json');
|
|
40
|
+
const temporary = `${path}.${randomBytes(16).toString('hex')}.tmp`;
|
|
41
|
+
try {
|
|
42
|
+
await writeFile(temporary, JSON.stringify({
|
|
43
|
+
correlation, session_id: validId(sessionId) ? sessionId : null,
|
|
44
|
+
observedAt: new Date().toISOString(),
|
|
45
|
+
}) + '\n', { flag: 'wx', mode: 0o600 });
|
|
46
|
+
await rename(temporary, path);
|
|
47
|
+
} finally {
|
|
48
|
+
await unlink(temporary).catch((error: NodeJS.ErrnoException) => { if (error.code !== 'ENOENT') throw error; });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function resolveAgentSessionIdentity(
|
|
53
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
54
|
+
worktree = findProjectRoot(),
|
|
55
|
+
): Promise<AgentSessionIdentity> {
|
|
56
|
+
const kind = resolveReportableSessionAgentKind(env);
|
|
57
|
+
try {
|
|
58
|
+
if (kind === 'claude') {
|
|
59
|
+
const correlation = claudeCorrelation(env);
|
|
60
|
+
if (!correlation) return { kind: 'unknown', reason: 'claude-launch-correlation-missing' };
|
|
61
|
+
const record = JSON.parse(await readFile(join(worktree, '.borgmcp', 'claude-session.json'), 'utf8'));
|
|
62
|
+
if (record?.correlation !== correlation) return { kind: 'unknown', reason: 'claude-launch-correlation-mismatch' };
|
|
63
|
+
if (!validId(record.session_id) || typeof record.observedAt !== 'string' || !Number.isFinite(Date.parse(record.observedAt))) {
|
|
64
|
+
return { kind: 'unknown', reason: 'claude-session-start-invalid' };
|
|
65
|
+
}
|
|
66
|
+
// Last observed hook, not proof of freshness: a skipped resume hook is
|
|
67
|
+
// undetectable without a future per-call harness session handoff.
|
|
68
|
+
return { kind: 'known', id: `claude:${record.session_id}`, source: 'claude-session-start', observedAt: record.observedAt };
|
|
69
|
+
}
|
|
70
|
+
if (kind === 'codex') {
|
|
71
|
+
const socket = codexAppServerSocketFromEnv(env);
|
|
72
|
+
if (!socket) return { kind: 'unknown', reason: 'codex-launch-socket-missing' };
|
|
73
|
+
const client = new CodexAppServerClient(socket);
|
|
74
|
+
try {
|
|
75
|
+
await client.connect();
|
|
76
|
+
const candidates: string[] = [];
|
|
77
|
+
for (const id of await client.loadedThreadIds()) {
|
|
78
|
+
const thread = await client.readThread(id);
|
|
79
|
+
// An unreadable candidate could be another user thread; do not guess.
|
|
80
|
+
if (!thread) return { kind: 'unknown', reason: 'codex-thread-unreadable' };
|
|
81
|
+
if (thread.ephemeral !== true && !isCodexSubagentSource(thread.source) &&
|
|
82
|
+
(thread.threadSource === undefined || thread.threadSource === 'user')) candidates.push(thread.id);
|
|
83
|
+
}
|
|
84
|
+
if (candidates.length !== 1 || !validId(candidates[0])) return { kind: 'unknown', reason: 'codex-user-thread-not-unique' };
|
|
85
|
+
return { kind: 'known', id: `codex:${candidates[0]}`, source: 'codex-launch-thread', observedAt: new Date().toISOString() };
|
|
86
|
+
} finally { client.close(); }
|
|
87
|
+
}
|
|
88
|
+
if (kind === 'opencode') {
|
|
89
|
+
const id = await resolveOpenCodeAgentSessionId();
|
|
90
|
+
return validId(id)
|
|
91
|
+
? { kind: 'known', id: `opencode:${id}`, source: 'opencode-launch-binding', observedAt: new Date().toISOString() }
|
|
92
|
+
: { kind: 'unknown', reason: 'opencode-launch-binding-unavailable-or-changed' };
|
|
93
|
+
}
|
|
94
|
+
return { kind: 'unknown', reason: 'agent-harness-unknown' };
|
|
95
|
+
} catch {
|
|
96
|
+
return { kind: 'unknown', reason: `${kind ?? 'agent'}-session-source-unavailable` };
|
|
97
|
+
}
|
|
98
|
+
}
|
package/src/assimilate-cmd.ts
CHANGED
|
@@ -10,20 +10,15 @@ import {
|
|
|
10
10
|
import { validateName } from './name-validator.js';
|
|
11
11
|
import { renderAssimilationWelcome } from './assimilate-welcome.js';
|
|
12
12
|
import { shellEscape } from './shell-escape.js';
|
|
13
|
-
import {
|
|
13
|
+
import { type CodexRemoteLaunch } from './codex-remote.js';
|
|
14
14
|
import {
|
|
15
15
|
buildAgentKickoffPrompt,
|
|
16
16
|
buildKickoffWakePathClause,
|
|
17
|
-
|
|
18
|
-
socketPathFromRemoteArgs,
|
|
17
|
+
buildCodexLaunchArgs,
|
|
19
18
|
} from './codex-launch.js';
|
|
20
19
|
import { perWorktreeBranchName, computeWorktreePath, localBranchExists, isMerged } from './worktree-lifecycle.js';
|
|
21
20
|
import { DroneEvictedError } from './drone-lifecycle.js';
|
|
22
|
-
import { codexBorgSessionConfigArgs } from './launch-gate.js';
|
|
23
21
|
import {
|
|
24
|
-
codexAgentKindConfigArgs,
|
|
25
|
-
codexRemoteWakeConfigArgs,
|
|
26
|
-
codexStateRootConfigArgs,
|
|
27
22
|
withAgentRuntimeEnv,
|
|
28
23
|
} from './agent-runtime.js';
|
|
29
24
|
import {
|
|
@@ -405,13 +400,6 @@ export interface AssimilateDeps {
|
|
|
405
400
|
setCliPreferenceForWorktree: (cli: BorgCli, worktree: string) => Promise<void>;
|
|
406
401
|
resolveCli: (explicit?: BorgCli) => Promise<BorgCli>;
|
|
407
402
|
prepareCodexRemoteLaunch: () => Promise<CodexRemoteLaunch>;
|
|
408
|
-
setCodexWakeTarget: (cubeId: string, droneId: string, target: { threadId: string; socketPath: string }) => Promise<void>;
|
|
409
|
-
findLoadedCodexThread: (options: {
|
|
410
|
-
socketPath: string;
|
|
411
|
-
cwd: string;
|
|
412
|
-
previewIncludes: string;
|
|
413
|
-
updatedAfter: number;
|
|
414
|
-
}) => Promise<string | null>;
|
|
415
403
|
|
|
416
404
|
}
|
|
417
405
|
|
|
@@ -952,13 +940,11 @@ export async function finalizeAssimilationSeat(
|
|
|
952
940
|
export type LaunchDeps = Pick<
|
|
953
941
|
AssimilateDeps,
|
|
954
942
|
| 'exec'
|
|
955
|
-
| 'findLoadedCodexThread'
|
|
956
943
|
| 'getInboxPath'
|
|
957
944
|
| 'isTTY'
|
|
958
945
|
| 'prepareCodexRemoteLaunch'
|
|
959
946
|
| 'probeMcpReady'
|
|
960
947
|
| 'resolveCliApprovals'
|
|
961
|
-
| 'setCodexWakeTarget'
|
|
962
948
|
| 'setTerminalTitle'
|
|
963
949
|
| 'stderr'
|
|
964
950
|
| 'stdout'
|
|
@@ -1019,17 +1005,14 @@ export async function launchAssimilatedAgent(
|
|
|
1019
1005
|
);
|
|
1020
1006
|
}
|
|
1021
1007
|
const inboxPath = deps.getInboxPath(result.cube_id, result.drone_id);
|
|
1022
|
-
const codexWakeNonce = cli === 'codex' ? `borg-wake-${randomUUID()}` : null;
|
|
1023
1008
|
const monitorClause = buildKickoffWakePathClause(
|
|
1024
1009
|
cli,
|
|
1025
1010
|
cli === 'claude' ? inboxPath : null,
|
|
1026
1011
|
cli === 'claude' ? monitorStateRoot : null,
|
|
1027
1012
|
);
|
|
1028
|
-
let codexWakePathClause: string | undefined;
|
|
1029
|
-
let remoteArgs: string[] = [];
|
|
1030
1013
|
let launchArgs: string[];
|
|
1031
|
-
let codexSocketPath: string | null = null;
|
|
1032
1014
|
let codexServerCleanup: (() => void) | null = null;
|
|
1015
|
+
let codexRemote: CodexRemoteLaunch | null = null;
|
|
1033
1016
|
const launchApproval = deps.resolveCliApprovals
|
|
1034
1017
|
? await deps.resolveCliApprovals(cli, agentCwd, { skipOverride: flags.noBorgApprovalOverride })
|
|
1035
1018
|
: { codexArgs: [] };
|
|
@@ -1050,39 +1033,36 @@ export async function launchAssimilatedAgent(
|
|
|
1050
1033
|
for (const key of modelEnv.unset) delete childEnv[key];
|
|
1051
1034
|
|
|
1052
1035
|
if (cli === 'codex') {
|
|
1053
|
-
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
} else {
|
|
1059
|
-
codexWakePathClause = 'Codex wake-path capability check passed: remote-control socket established for this session.';
|
|
1036
|
+
codexRemote = await deps.prepareCodexRemoteLaunch();
|
|
1037
|
+
if (!codexRemote.ready) {
|
|
1038
|
+
const stderr = codexRemote.stderr ? `\n${codexRemote.stderr}` : '';
|
|
1039
|
+
deps.stderr(`Codex launch refused: ${codexRemote.reason}${stderr}\n`);
|
|
1040
|
+
return 1;
|
|
1060
1041
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
codexSocketPath = socketPathFromRemoteArgs(remote.args);
|
|
1064
|
-
codexServerCleanup = remote.server?.cleanup ?? null;
|
|
1042
|
+
Object.assign(childEnv, codexRemote.env);
|
|
1043
|
+
codexServerCleanup = codexRemote.server.cleanup;
|
|
1065
1044
|
}
|
|
1066
1045
|
const kickoff = buildAgentKickoffPrompt({
|
|
1067
1046
|
cli,
|
|
1068
|
-
codexWakeNonce,
|
|
1069
1047
|
monitorClause,
|
|
1070
|
-
codexWakePathClause,
|
|
1071
1048
|
});
|
|
1072
1049
|
let openCodeKickoff: ReturnType<typeof createOpenCodeLaunchKickoff> | null = null;
|
|
1073
1050
|
let dronePort: number | undefined;
|
|
1074
1051
|
launchArgs = [kickoff];
|
|
1075
1052
|
if (cli === 'codex') {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1053
|
+
const plan = buildCodexLaunchArgs({
|
|
1054
|
+
remote: codexRemote!,
|
|
1055
|
+
cwd: agentCwd,
|
|
1056
|
+
kickoff,
|
|
1057
|
+
approvalArgs: launchApproval.codexArgs,
|
|
1058
|
+
accessArgs: codexLaunchDirectoryArgs(launchAccessPaths),
|
|
1059
|
+
});
|
|
1060
|
+
if (!plan.ready) {
|
|
1061
|
+
codexServerCleanup?.();
|
|
1062
|
+
deps.stderr(`Codex launch refused: ${plan.reason}\n`);
|
|
1063
|
+
return 1;
|
|
1064
|
+
}
|
|
1065
|
+
launchArgs = plan.args;
|
|
1086
1066
|
} else if (cli === 'opencode') {
|
|
1087
1067
|
dronePort = await allocateOpenCodePort();
|
|
1088
1068
|
childEnv.BORG_OPENCODE_PORT = String(dronePort);
|
|
@@ -1094,17 +1074,12 @@ export async function launchAssimilatedAgent(
|
|
|
1094
1074
|
launchArgs = buildOpenCodeLaunchArgs(agentCwd, dronePort, openCodeKickoff.prompt);
|
|
1095
1075
|
}
|
|
1096
1076
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
socketPath: codexSocketPath,
|
|
1104
|
-
cwd: agentCwd,
|
|
1105
|
-
previewNeedle: codexWakeNonce,
|
|
1106
|
-
launchedAtSeconds: Math.floor(Date.now() / 1000),
|
|
1107
|
-
});
|
|
1077
|
+
let exitPromise: Promise<number>;
|
|
1078
|
+
try {
|
|
1079
|
+
exitPromise = deps.exec(cli, launchArgs, agentCwd, childEnv);
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
codexServerCleanup?.();
|
|
1082
|
+
throw error;
|
|
1108
1083
|
}
|
|
1109
1084
|
if (cli === 'opencode' && openCodeKickoff) {
|
|
1110
1085
|
const launchKickoff = openCodeKickoff;
|
|
@@ -1117,12 +1092,14 @@ export async function launchAssimilatedAgent(
|
|
|
1117
1092
|
launchIdentity: launchKickoff.correlationIdentity,
|
|
1118
1093
|
}).then(() => injectInitialKickoff(launchKickoff)).catch(() => {});
|
|
1119
1094
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1095
|
+
let exitCode: number;
|
|
1096
|
+
try {
|
|
1097
|
+
exitCode = await exitPromise;
|
|
1098
|
+
} finally {
|
|
1122
1099
|
try {
|
|
1123
|
-
codexServerCleanup();
|
|
1100
|
+
codexServerCleanup?.();
|
|
1124
1101
|
} catch {
|
|
1125
|
-
// Best-effort cleanup after
|
|
1102
|
+
// Best-effort cleanup after Codex exits or fails to spawn.
|
|
1126
1103
|
}
|
|
1127
1104
|
}
|
|
1128
1105
|
if (spawnedWorktreePath && originalCwd !== spawnedWorktreePath) {
|
package/src/assimilate-deps.ts
CHANGED
|
@@ -61,7 +61,6 @@ import {
|
|
|
61
61
|
hasPersistedActiveCube as cubesHasPersistedActive,
|
|
62
62
|
inboxPathForDrone,
|
|
63
63
|
setProjectCliPreference,
|
|
64
|
-
setCodexWakeTarget,
|
|
65
64
|
} from './cubes.js';
|
|
66
65
|
import {
|
|
67
66
|
addProjectSessionStartHook,
|
|
@@ -71,7 +70,6 @@ import { findPendingServerEnrollment, listServerCredentialOrigins } from './conf
|
|
|
71
70
|
import { setTerminalTitle as setTitle } from './terminal-title.js';
|
|
72
71
|
import { defaultCliChoiceDeps, resolveCliChoice } from './cli-platform.js';
|
|
73
72
|
import { prepareCodexRemoteLaunch, defaultCodexRemoteDeps } from './codex-remote.js';
|
|
74
|
-
import { findLoadedCodexThread } from './codex-app-server.js';
|
|
75
73
|
import { defaultApprovalIo, resolveLaunchBorgApprovals } from './cli-tool-approval.js';
|
|
76
74
|
import { ensurePrivateBorgConfigRoot } from './private-root.js';
|
|
77
75
|
import {
|
|
@@ -577,7 +575,5 @@ export function buildDefaultAssimilateDeps(
|
|
|
577
575
|
),
|
|
578
576
|
setCliPreferenceForWorktree: (cli, worktree) => setProjectCliPreference(cli, worktree),
|
|
579
577
|
prepareCodexRemoteLaunch: () => prepareCodexRemoteLaunch(defaultCodexRemoteDeps()),
|
|
580
|
-
setCodexWakeTarget,
|
|
581
|
-
findLoadedCodexThread,
|
|
582
578
|
};
|
|
583
579
|
}
|
package/src/claude.ts
CHANGED
|
@@ -16,7 +16,6 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { spawn } from 'child_process';
|
|
19
|
-
import { randomUUID } from 'node:crypto';
|
|
20
19
|
import { realpathSync } from 'node:fs';
|
|
21
20
|
import { homedir } from 'node:os';
|
|
22
21
|
import { basename } from 'node:path';
|
|
@@ -30,8 +29,6 @@ import {
|
|
|
30
29
|
getActiveCube,
|
|
31
30
|
inboxPathForDrone,
|
|
32
31
|
LaunchSeatIdentityChangedError,
|
|
33
|
-
setCodexWakeTarget,
|
|
34
|
-
pruneDeadCodexWakeTargets,
|
|
35
32
|
type ActiveCube,
|
|
36
33
|
type BorgCli,
|
|
37
34
|
} from './cubes.js';
|
|
@@ -82,22 +79,15 @@ import { setTerminalTitle } from './terminal-title.js';
|
|
|
82
79
|
import { initConsolePrefix, consolePrefix } from './console-prefix.js';
|
|
83
80
|
import { initDebugFromArgv } from './debug.js';
|
|
84
81
|
import { configuredCliNames, defaultCliChoiceDeps, detectCliAvailability, detectCliConfiguration, parseCliFlag, resolveCliChoice } from './cli-platform.js';
|
|
85
|
-
import { prepareCodexRemoteLaunch, resolveCodexLaunchCwd,
|
|
82
|
+
import { prepareCodexRemoteLaunch, resolveCodexLaunchCwd, defaultCodexRemoteDeps } from './codex-remote.js';
|
|
86
83
|
import {
|
|
87
|
-
BORG_CODEX_REMOTE_WAKE_ENV,
|
|
88
|
-
codexAgentKindConfigArgs,
|
|
89
|
-
codexRemoteWakeConfigArgs,
|
|
90
|
-
codexStateRootConfigArgs,
|
|
91
84
|
withAgentRuntimeEnv,
|
|
92
85
|
} from './agent-runtime.js';
|
|
93
|
-
import { findLoadedCodexThread } from './codex-app-server.js';
|
|
94
86
|
import {
|
|
95
87
|
buildAgentKickoffPrompt,
|
|
96
88
|
buildKickoffWakePathClause,
|
|
97
|
-
|
|
98
|
-
socketPathFromRemoteArgs,
|
|
89
|
+
buildCodexLaunchArgs,
|
|
99
90
|
} from './codex-launch.js';
|
|
100
|
-
import { codexBorgSessionConfigArgs } from './launch-gate.js';
|
|
101
91
|
import {
|
|
102
92
|
addCodexSessionStartHook,
|
|
103
93
|
addCodexUserPromptSubmitHook,
|
|
@@ -639,9 +629,6 @@ async function main() {
|
|
|
639
629
|
: null
|
|
640
630
|
);
|
|
641
631
|
|
|
642
|
-
const codexWakeNonce = cli === 'codex' ? `borg-wake-${randomUUID()}` : null;
|
|
643
|
-
let codexWakePathClause: string | undefined;
|
|
644
|
-
let remoteArgs: string[] = [];
|
|
645
632
|
// gh#673 P1: mark the agent session as borg-launched. Claude Code's MCP
|
|
646
633
|
// child + hook commands inherit this env, gating the borg activation
|
|
647
634
|
// surface (launch-gate.ts). ACTIVATION-only — never a security gate.
|
|
@@ -654,45 +641,33 @@ async function main() {
|
|
|
654
641
|
if (cli === 'opencode' && launchApproval.openCodePermission) {
|
|
655
642
|
launchEnv.OPENCODE_PERMISSION = launchApproval.openCodePermission;
|
|
656
643
|
}
|
|
657
|
-
let codexSocketPath: string | null = null;
|
|
658
644
|
let codexServerCleanup: (() => void) | null = null;
|
|
659
|
-
|
|
645
|
+
let codexRemote: Awaited<ReturnType<typeof prepareCodexRemoteLaunch>> | null = null;
|
|
646
|
+
if (cli === 'codex') {
|
|
647
|
+
if (passthroughArgs.some((arg) => arg === '--remote' || arg.startsWith('--remote='))) {
|
|
648
|
+
console.error(`${consolePrefix()}${chalk.red('Codex launch refused: Borg owns Codex remote control; remove the passthrough --remote option and retry.')}`);
|
|
649
|
+
process.exit(1);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
660
652
|
console.error(`${consolePrefix()}${chalk.gray('◼ Starting Codex remote-wake app-server…')}`);
|
|
661
|
-
|
|
662
|
-
if (
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
codexWakePathClause =
|
|
668
|
-
`Codex wake-path capability check passed: remote-control socket established for this session.`;
|
|
653
|
+
codexRemote = await prepareCodexRemoteLaunch(defaultCodexRemoteDeps());
|
|
654
|
+
if (!codexRemote.ready) {
|
|
655
|
+
const stderr = codexRemote.stderr ? `\n${codexRemote.stderr}` : '';
|
|
656
|
+
console.error(`${consolePrefix()}${chalk.red(`Codex launch refused: ${codexRemote.reason}${stderr}`)}`);
|
|
657
|
+
process.exit(1);
|
|
658
|
+
return;
|
|
669
659
|
}
|
|
670
|
-
remoteArgs = remote.args;
|
|
671
660
|
launchEnv = {
|
|
672
661
|
...withAgentRuntimeEnv(process.env, cli),
|
|
673
|
-
...
|
|
662
|
+
...codexRemote.env,
|
|
674
663
|
BORG_SESSION: '1',
|
|
675
664
|
};
|
|
676
|
-
|
|
677
|
-
codexServerCleanup = remote.server?.cleanup ?? null;
|
|
678
|
-
} else if (cli === 'codex' && passthroughArgs.includes('--remote')) {
|
|
679
|
-
codexWakePathClause =
|
|
680
|
-
`Codex wake-path capability check: using caller-provided --remote socket; if no wake arrives, run borg_regen manually when returning to the session.`;
|
|
681
|
-
codexSocketPath = socketPathFromRemoteArgs(passthroughArgs);
|
|
682
|
-
if (codexSocketPath) {
|
|
683
|
-
launchEnv = {
|
|
684
|
-
...withAgentRuntimeEnv(process.env, cli),
|
|
685
|
-
[BORG_CODEX_REMOTE_WAKE_ENV]: '1',
|
|
686
|
-
BORG_SESSION: '1',
|
|
687
|
-
};
|
|
688
|
-
}
|
|
665
|
+
codexServerCleanup = codexRemote.server.cleanup;
|
|
689
666
|
}
|
|
690
667
|
|
|
691
668
|
const kickoff = buildAgentKickoffPrompt({
|
|
692
669
|
cli,
|
|
693
|
-
codexWakeNonce,
|
|
694
670
|
monitorClause,
|
|
695
|
-
codexWakePathClause,
|
|
696
671
|
});
|
|
697
672
|
// This stays separate from the shared kickoff so OpenCode can carry
|
|
698
673
|
// launch-only API and correlation identities outside argv and prompt text.
|
|
@@ -700,29 +675,26 @@ async function main() {
|
|
|
700
675
|
let openCodePort: number | undefined;
|
|
701
676
|
let launchArgs: string[];
|
|
702
677
|
if (cli === 'codex') {
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
...remoteArgs,
|
|
719
|
-
...withCodexCwdArg([...passthroughArgs, kickoff], process.cwd()),
|
|
720
|
-
];
|
|
678
|
+
const plan = buildCodexLaunchArgs({
|
|
679
|
+
remote: codexRemote!,
|
|
680
|
+
cwd: process.cwd(),
|
|
681
|
+
kickoff,
|
|
682
|
+
approvalArgs: launchApproval.codexArgs,
|
|
683
|
+
seatExpectationArgs: codexLaunchSeatExpectationConfigArgs(),
|
|
684
|
+
passthroughArgs,
|
|
685
|
+
});
|
|
686
|
+
if (!plan.ready) {
|
|
687
|
+
codexServerCleanup?.();
|
|
688
|
+
console.error(`${consolePrefix()}${chalk.red(`Codex launch refused: ${plan.reason}`)}`);
|
|
689
|
+
process.exit(1);
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
launchArgs = plan.args;
|
|
721
693
|
} else if (cli === 'opencode') {
|
|
722
694
|
// OpenCode launch: start TUI with the kickoff passed via --prompt
|
|
723
695
|
// (auto-submits it as the first message). BORG_SESSION is pinned in
|
|
724
|
-
// opencode.json.
|
|
725
|
-
//
|
|
696
|
+
// opencode.json. Wake delivery uses HTTP entry injection through OpenCode's local HTTP API;
|
|
697
|
+
// the OS-selected port avoids a deterministic collision space.
|
|
726
698
|
openCodePort = await allocateOpenCodePort();
|
|
727
699
|
installBorgPlugin();
|
|
728
700
|
openCodeKickoff = createOpenCodeLaunchKickoff(kickoff);
|
|
@@ -749,25 +721,6 @@ async function main() {
|
|
|
749
721
|
})).process
|
|
750
722
|
: spawn(cli, launchArgs, { stdio: 'inherit', shell: false, env: launchEnv });
|
|
751
723
|
|
|
752
|
-
// gh#857 WI-2: wake-target recording is codex-only (app-server bridge).
|
|
753
|
-
// OpenCode uses HTTP entry injection; Claude uses the inbox Monitor.
|
|
754
|
-
if (cli === 'codex' && active && codexSocketPath) {
|
|
755
|
-
void recordCodexWakeTarget({
|
|
756
|
-
deps: { setCodexWakeTarget, findLoadedCodexThread },
|
|
757
|
-
cubeId: active.cubeId,
|
|
758
|
-
droneId: active.droneId,
|
|
759
|
-
socketPath: codexSocketPath,
|
|
760
|
-
passthroughArgs,
|
|
761
|
-
previewNeedle: codexWakeNonce ?? kickoff.slice(0, 120),
|
|
762
|
-
cwd: process.cwd(),
|
|
763
|
-
launchedAtSeconds: Math.floor(Date.now() / 1000),
|
|
764
|
-
});
|
|
765
|
-
// gh#855: self-heal the wake-target file — drop entries whose app-server
|
|
766
|
-
// socket is positively dead (crashed prior launches), mirroring the
|
|
767
|
-
// socket-dir pruneStaleSockets. Best-effort; never blocks the launch.
|
|
768
|
-
void pruneDeadCodexWakeTargets((sock) => checkCodexBridgeHealthy(sock));
|
|
769
|
-
}
|
|
770
|
-
|
|
771
724
|
agentProcess.on('error', (err: NodeJS.ErrnoException) => {
|
|
772
725
|
if (codexServerCleanup) {
|
|
773
726
|
try {
|
package/src/codex-app-server.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface CodexThreadSummary {
|
|
|
7
7
|
preview: string;
|
|
8
8
|
status: { type: string };
|
|
9
9
|
updatedAt: number;
|
|
10
|
+
source?: unknown;
|
|
11
|
+
ephemeral?: boolean;
|
|
12
|
+
threadSource?: string;
|
|
13
|
+
turns?: unknown[];
|
|
10
14
|
}
|
|
11
15
|
|
|
12
16
|
interface PendingRequest {
|
|
@@ -85,6 +89,10 @@ export class CodexAppServerClient {
|
|
|
85
89
|
preview: thread.preview,
|
|
86
90
|
status: thread.status,
|
|
87
91
|
updatedAt: thread.updatedAt,
|
|
92
|
+
source: thread.source,
|
|
93
|
+
ephemeral: thread.ephemeral,
|
|
94
|
+
threadSource: thread.threadSource,
|
|
95
|
+
turns: thread.turns,
|
|
88
96
|
};
|
|
89
97
|
}
|
|
90
98
|
|
|
@@ -159,31 +167,6 @@ export class CodexAppServerClient {
|
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
169
|
|
|
162
|
-
export async function findLoadedCodexThread(options: {
|
|
163
|
-
socketPath: string;
|
|
164
|
-
cwd: string;
|
|
165
|
-
previewIncludes: string;
|
|
166
|
-
updatedAfter: number;
|
|
167
|
-
}): Promise<string | null> {
|
|
168
|
-
const client = new CodexAppServerClient(options.socketPath);
|
|
169
|
-
await client.connect();
|
|
170
|
-
try {
|
|
171
|
-
const ids = await client.loadedThreadIds();
|
|
172
|
-
let best: CodexThreadSummary | null = null;
|
|
173
|
-
for (const id of ids) {
|
|
174
|
-
const thread = await client.readThread(id);
|
|
175
|
-
if (!thread) continue;
|
|
176
|
-
if (thread.cwd !== options.cwd) continue;
|
|
177
|
-
if (!thread.preview.includes(options.previewIncludes)) continue;
|
|
178
|
-
if (thread.updatedAt < options.updatedAfter) continue;
|
|
179
|
-
if (!best || thread.updatedAt > best.updatedAt) best = thread;
|
|
180
|
-
}
|
|
181
|
-
return best?.id ?? null;
|
|
182
|
-
} finally {
|
|
183
|
-
client.close();
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
170
|
function encodeWebSocketTextFrame(text: string): Buffer {
|
|
188
171
|
const payload = Buffer.from(text);
|
|
189
172
|
let header: Buffer;
|