@cjhyy/code-shell-capability-coding 0.8.13 → 0.8.20

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.
@@ -10,6 +10,9 @@ export interface AgentRunResult {
10
10
  export declare function runWithLines(adapter: AgentAdapter, lines: string[], exitCode: number | null): AgentRunResult;
11
11
  export interface DriverRunOpts extends Omit<BuildArgsOpts, "permissionMode"> {
12
12
  permissionMode?: PermissionMode;
13
+ /** Called once as soon as the external CLI reports its durable session/thread
14
+ * id. This normally happens near process startup, well before the turn exits. */
15
+ onSessionId?: (sessionId: string) => void;
13
16
  }
14
17
  export declare function detectCodexImageInput(command: string, cwd: string, signal?: AbortSignal): Promise<boolean>;
15
18
  /** Spawn ONE headless agent run, collect stream-json to exit, return result.
@@ -233,6 +233,20 @@ export function runAgentOnce(adapter, opts, signal) {
233
233
  detached: false,
234
234
  stdio: [viaStdin ? "pipe" : "ignore", "pipe", "pipe"],
235
235
  });
236
+ let reportedSessionId = "";
237
+ const reportSessionId = (candidate) => {
238
+ if (reportedSessionId || typeof candidate !== "string" || !candidate.trim())
239
+ return;
240
+ reportedSessionId = candidate.trim();
241
+ try {
242
+ opts.onSessionId?.(reportedSessionId);
243
+ }
244
+ catch {
245
+ // Session observation is best-effort metadata. A UI/persistence
246
+ // listener must never be able to fail the external agent run.
247
+ }
248
+ };
249
+ reportSessionId(opts.resumeSessionId);
236
250
  let settled = false;
237
251
  let abortRequested = false;
238
252
  let termination;
@@ -254,7 +268,18 @@ export function runAgentOnce(adapter, opts, signal) {
254
268
  const lines = [];
255
269
  if (child.stdout) {
256
270
  const rl = createInterface({ input: child.stdout });
257
- rl.on("line", (line) => lines.push(line));
271
+ rl.on("line", (line) => {
272
+ lines.push(line);
273
+ if (!reportedSessionId) {
274
+ try {
275
+ reportSessionId(adapter.parseResult([line]).sessionId);
276
+ }
277
+ catch {
278
+ // Keep collecting output. The complete parse at exit remains the
279
+ // source of truth and will surface malformed output normally.
280
+ }
281
+ }
282
+ });
258
283
  }
259
284
  child.on("error", (err) => {
260
285
  if (settled)
@@ -276,7 +301,9 @@ export function runAgentOnce(adapter, opts, signal) {
276
301
  settled = true;
277
302
  cleanup();
278
303
  void (termination ?? Promise.resolve()).then(() => {
279
- resolve(runWithLines(adapter, lines, code));
304
+ const result = runWithLines(adapter, lines, code);
305
+ reportSessionId(result.sessionId);
306
+ resolve(result);
280
307
  });
281
308
  });
282
309
  })().catch(reject);
@@ -9,5 +9,7 @@ export interface ExternalRuntimeTurnInput {
9
9
  text: string;
10
10
  clientMessageId?: string;
11
11
  attachments?: readonly ExternalRuntimeAttachment[];
12
+ /** Host-injected continuation (for example, a background-job completion). */
13
+ injected?: boolean;
12
14
  }
13
15
  export declare function textWithAttachmentReferences(input: ExternalRuntimeTurnInput): string;
@@ -18,6 +18,7 @@ type Runner = (opts: {
18
18
  permissionMode?: PermMode;
19
19
  signal?: AbortSignal;
20
20
  imagePaths?: string[];
21
+ onSessionId?: (sessionId: string) => void;
21
22
  }) => Promise<AgentRunResult>;
22
23
  type SessionStore = {
23
24
  get(cli: DriveCli, sessionId: string): ExternalAgentSessionBinding | undefined;
@@ -46,6 +47,7 @@ type LegacyRunner = (opts: {
46
47
  cwd: string;
47
48
  permissionMode?: PermMode;
48
49
  signal?: AbortSignal;
50
+ onSessionId?: (sessionId: string) => void;
49
51
  }) => Promise<AgentRunResult>;
50
52
  export declare function makeDriveClaudeCodeTool(runner?: LegacyRunner, options?: DriveAgentToolOptions): (args: Record<string, unknown>, ctx?: ToolContext) => Promise<string>;
51
53
  export declare const driveClaudeCodeTool: (args: Record<string, unknown>, ctx?: ToolContext) => Promise<string>;
@@ -124,6 +124,7 @@ const defaultRunner = (opts) => {
124
124
  cwd: opts.cwd,
125
125
  permissionMode: opts.permissionMode ?? "default",
126
126
  imagePaths: opts.imagePaths,
127
+ onSessionId: opts.onSessionId,
127
128
  }, opts.signal);
128
129
  };
129
130
  function newDriveJobId() {
@@ -167,8 +168,7 @@ function hasRunningDriveWriter(workspaceCwd, workspaceRoot) {
167
168
  return true;
168
169
  }
169
170
  for (const lease of foregroundDriveLeases.values()) {
170
- if (lease.effectiveWorkspaceRoot === workspaceRoot ||
171
- lease.workspaceRoot === workspaceRoot) {
171
+ if (lease.effectiveWorkspaceRoot === workspaceRoot || lease.workspaceRoot === workspaceRoot) {
172
172
  return true;
173
173
  }
174
174
  }
@@ -325,7 +325,8 @@ function formatDriveJobListLine(job) {
325
325
  `${job.jobId}`,
326
326
  `status=${job.status}`,
327
327
  `cli=${cli}`,
328
- `session=${job.sessionId}`,
328
+ `ownerSession=${job.sessionId}`,
329
+ `externalSessionId=${job.ccSessionId ?? "pending"}`,
329
330
  `launchCwd=${launchCwd}`,
330
331
  ...(job.worktreePath ? [`worktree=${job.worktreePath}`] : []),
331
332
  ...(job.worktreeBranch ? [`branch=${job.worktreeBranch}`] : []),
@@ -648,7 +649,11 @@ function trackBackgroundRun(params) {
648
649
  return { error: "Error: DriveAgent owner session closed before the job could start." };
649
650
  }
650
651
  try {
651
- run = params.start();
652
+ run = params.start((externalSessionId) => {
653
+ if (!isValidSessionId(externalSessionId))
654
+ return;
655
+ backgroundJobRegistry.recordArtifacts(jobId, { ccSessionId: externalSessionId });
656
+ });
652
657
  }
653
658
  catch (error) {
654
659
  const message = error instanceof Error ? error.message : String(error);
@@ -713,10 +718,19 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
713
718
  ? "default"
714
719
  : "bypassPermissions";
715
720
  const isWritableRun = permissionMode !== "default";
716
- // External runtimes do not participate in the native Engine's wake-up loop.
717
- // A detached result would be queued for an Engine that is not driving this
718
- // turn, so keep delegation attached to the parent runtime.
719
- const background = isExternalRuntimeContext(ctx) ? false : args.background !== false;
721
+ const externalRuntime = isExternalRuntimeContext(ctx);
722
+ const background = args.background !== false;
723
+ // A detached external-runtime job is safe only when its host explicitly
724
+ // promises to drain notificationQueue and inject the result into a later
725
+ // runtime turn. Silently changing background:true into a foreground wait is
726
+ // incorrect: Codex's yieldable exec wrapper can return control to the model
727
+ // while the nested MCP call remains pending, producing concurrent work that
728
+ // looks as though it came from the delegated agent.
729
+ if (externalRuntime && background && ctx?.externalRuntimeBackgroundDelivery !== true) {
730
+ return (`Error: background ${cli === "codex" ? "Codex" : "Claude Code"} delegation is not ` +
731
+ "supported by this external-runtime host because it cannot deliver the completion. " +
732
+ "Retry with background:false.");
733
+ }
720
734
  const cliName = cli === "codex" ? "Codex" : "Claude Code";
721
735
  if (background && !isValidSessionId(ctx?.sessionId)) {
722
736
  return `Error: cannot start a background ${cliName} job without a session — its result notification would be dropped. Retry with background:false, or ensure the tool runs inside a session.`;
@@ -867,7 +881,7 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
867
881
  permissionMode,
868
882
  imagePaths,
869
883
  };
870
- const foregroundHandoffMs = isExternalRuntimeContext(ctx)
884
+ const foregroundHandoffMs = externalRuntime
871
885
  ? Number.POSITIVE_INFINITY
872
886
  : (options.foregroundHandoffMs ?? DRIVE_AGENT_FOREGROUND_HANDOFF_MS);
873
887
  if (background) {
@@ -888,7 +902,11 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
888
902
  isolation,
889
903
  worktree: managedWorktree,
890
904
  promptSummary,
891
- start: () => startRun(runner, { ...runOptsBase, signal: abortController.signal }),
905
+ start: (onSessionId) => startRun(runner, {
906
+ ...runOptsBase,
907
+ signal: abortController.signal,
908
+ onSessionId,
909
+ }),
892
910
  abort: () => abortController.abort(),
893
911
  sessionStore,
894
912
  writable: isWritableRun,
@@ -904,6 +922,11 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
904
922
  return [
905
923
  resumeNote,
906
924
  `已在后台启动 ${cliName}(jobId ${tracked.jobId})。完成后会通知你结果,无需轮询。`,
925
+ ...(externalRuntime
926
+ ? [
927
+ "除非用户明确要求并行工作,否则不要在本地重复执行已委派的任务;请结束当前回复,等待完成通知触发续接。",
928
+ ]
929
+ : []),
907
930
  worktreeStartNote(managedWorktree),
908
931
  ]
909
932
  .filter(Boolean)
@@ -921,7 +944,16 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
921
944
  return appendLifecycleNote(lease.error, lifecycle);
922
945
  }
923
946
  const foregroundAbort = makeAbortController(callerSignal, true);
924
- const run = startRun(runner, { ...runOptsBase, signal: foregroundAbort.signal });
947
+ let liveExternalSessionId;
948
+ let backgroundSessionIdSink;
949
+ const run = startRun(runner, {
950
+ ...runOptsBase,
951
+ signal: foregroundAbort.signal,
952
+ onSessionId: (sessionId) => {
953
+ liveExternalSessionId = sessionId;
954
+ backgroundSessionIdSink?.(sessionId);
955
+ },
956
+ });
925
957
  let result;
926
958
  try {
927
959
  result = await waitForForegroundOrHandoff(run, foregroundHandoffMs);
@@ -947,7 +979,12 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
947
979
  isolation,
948
980
  worktree: managedWorktree,
949
981
  promptSummary,
950
- start: () => run,
982
+ start: (onSessionId) => {
983
+ backgroundSessionIdSink = onSessionId;
984
+ if (liveExternalSessionId)
985
+ onSessionId(liveExternalSessionId);
986
+ return run;
987
+ },
951
988
  abort: () => foregroundAbort.abort(),
952
989
  sessionStore,
953
990
  writable: isWritableRun,
@@ -1100,7 +1137,8 @@ function inspectDriveAgentJob(jobId) {
1100
1137
  `jobId: ${job.jobId}`,
1101
1138
  `status: ${job.status}`,
1102
1139
  `cli: ${job.cli ?? "unknown"}`,
1103
- `session: ${job.sessionId}`,
1140
+ `ownerSession: ${job.sessionId}`,
1141
+ `externalSessionId: ${job.ccSessionId ?? "pending"}`,
1104
1142
  `launchCwd: ${job.launchCwd ?? job.cwd ?? "(unknown cwd)"}`,
1105
1143
  `startedAt: ${new Date(job.startedAt).toISOString()}`,
1106
1144
  `duration: ${jobDurationSeconds(job)}`,
@@ -1121,8 +1159,6 @@ function inspectDriveAgentJob(jobId) {
1121
1159
  lines.push(`worktreeLifecycle: ${job.worktreeLifecycle}`);
1122
1160
  if (job.finishedAt !== undefined)
1123
1161
  lines.push(`finishedAt: ${new Date(job.finishedAt).toISOString()}`);
1124
- if (job.ccSessionId)
1125
- lines.push(`ccSessionId: ${job.ccSessionId}`);
1126
1162
  if (job.changedFiles && job.changedFiles.length > 0) {
1127
1163
  lines.push("changedFiles:", ...job.changedFiles.map((file) => `- ${file}`));
1128
1164
  }
@@ -1275,7 +1311,7 @@ export const driveClaudeCodeToolDef = {
1275
1311
  };
1276
1312
  export function makeDriveClaudeCodeTool(runner, options) {
1277
1313
  const generic = runner
1278
- ? ({ prompt, resumeSessionId, model, cwd, permissionMode, signal }) => runner({ prompt, resumeSessionId, model, cwd, permissionMode, signal })
1314
+ ? ({ prompt, resumeSessionId, model, cwd, permissionMode, signal, onSessionId }) => runner({ prompt, resumeSessionId, model, cwd, permissionMode, signal, onSessionId })
1279
1315
  : undefined;
1280
1316
  return makeDriveAgentTool(generic ?? defaultRunner, "claude", options);
1281
1317
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-capability-coding",
3
- "version": "0.8.13",
3
+ "version": "0.8.20",
4
4
  "description": "Coding capability pack for the generic code-shell agent core.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,7 +39,7 @@
39
39
  "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
40
40
  },
41
41
  "dependencies": {
42
- "@cjhyy/code-shell-core": "0.8.13"
42
+ "@cjhyy/code-shell-core": "0.8.20"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=20.10"