@cjhyy/code-shell-capability-coding 0.9.0 → 0.9.2

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.
@@ -275,6 +275,23 @@ function safeFinalizeDriveWorktree(managed) {
275
275
  };
276
276
  }
277
277
  }
278
+ /**
279
+ * Finalize a managed worktree once `run` settles, never before.
280
+ *
281
+ * Aborting a run only starts the driver's async process-tree teardown, so the
282
+ * external CLI can still be holding the worktree (and git's index.lock) when
283
+ * abort() returns. Cleaning up synchronously races that; waiting for the run to
284
+ * settle does not. Doubles as the rejection sink for an aborted run.
285
+ */
286
+ function finalizeDriveWorktreeAfter(run, managed) {
287
+ void run
288
+ .catch(() => undefined)
289
+ .then(() => {
290
+ // safeFinalizeDriveWorktree already degrades a failed cleanup to a
291
+ // "kept" note rather than throwing, so nothing can escape this chain.
292
+ safeFinalizeDriveWorktree(managed);
293
+ });
294
+ }
278
295
  function summarizePrompt(prompt, max = 120) {
279
296
  const oneLine = prompt.replace(/\s+/g, " ").trim();
280
297
  if (oneLine.length <= max)
@@ -607,6 +624,16 @@ function attachDriveCompletion(params) {
607
624
  });
608
625
  }
609
626
  function trackBackgroundRun(params) {
627
+ const { sessionId } = params;
628
+ if (!isValidSessionId(sessionId)) {
629
+ // Mirrors the background:true refusal above: a job whose completion
630
+ // notification cannot be routed would run and then vanish. Say so instead
631
+ // of registering work nobody can track. The caller aborts the run and
632
+ // finalizes any managed worktree on this error path.
633
+ return {
634
+ error: "Error: cannot track this DriveAgent run without a session — its result notification would be dropped. Re-run inside a session, or keep the task short enough to finish in the foreground.",
635
+ };
636
+ }
610
637
  const conflict = duplicateCwdError(params.effectiveWorkspaceCwd, params.effectiveWorkspaceRoot, params.writable);
611
638
  if (conflict)
612
639
  return { error: conflict };
@@ -616,7 +643,7 @@ function trackBackgroundRun(params) {
616
643
  // Publish ownership before starting the external process. Registry
617
644
  // listeners are synchronous and may re-enter session teardown from the
618
645
  // start notification; starting first would leave that process orphaned.
619
- backgroundJobRegistry.start(jobId, params.sessionId, params.label, {
646
+ backgroundJobRegistry.start(jobId, sessionId, params.label, {
620
647
  kind: "drive-agent",
621
648
  launchCwd: params.cwd,
622
649
  effectiveWorkspaceCwd: params.effectiveWorkspaceCwd,
@@ -660,7 +687,7 @@ function trackBackgroundRun(params) {
660
687
  backgroundJobRegistry.finish(jobId, { status: "failed", finalText: message });
661
688
  return { error: `Error: failed to start DriveAgent job: ${message}` };
662
689
  }
663
- attachDriveCompletion({ ...params, jobId, run });
690
+ attachDriveCompletion({ ...params, sessionId, jobId, run });
664
691
  return { jobId };
665
692
  }
666
693
  async function waitForForegroundOrHandoff(run, handoffMs) {
@@ -963,13 +990,26 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
963
990
  lease.release();
964
991
  return appendLifecycleNote(`${cliName} 运行出错:${error instanceof Error ? error.message : String(error)}`, lifecycle);
965
992
  }
966
- if (result.kind === "handoff" && isValidSessionId(ctx?.sessionId)) {
993
+ if (result.kind === "handoff") {
994
+ // Handing off is NOT gated on a valid ctx.sessionId. Gating it there left
995
+ // a session-less foreground run falling through to `await run`, blocking
996
+ // until the 30min tool cap whose abort kills the external CLI — the work
997
+ // was destroyed with no jobId and no notification path. trackBackgroundRun
998
+ // owns the session check instead: it registers a tracked job when the
999
+ // session can receive the completion, and otherwise returns an error the
1000
+ // branch below turns into an abort + deferred cleanup + explanation.
1001
+ //
967
1002
  // Registration below is synchronous. Release the foreground lease and
968
1003
  // replace it with the background registry entry in the same event-loop
969
- // turn, so another dispatch cannot slip into a gap.
1004
+ // turn, so another dispatch cannot slip into a gap. The same invariant
1005
+ // covers the failure path: the lease is dropped and no job exists between
1006
+ // here and the return below, so that span MUST stay synchronous — an
1007
+ // await would open the gap to a concurrent writable dispatch. Worktree
1008
+ // finalization is deliberately NOT in that span: it is deferred to run
1009
+ // settlement, after this turn has already returned.
970
1010
  lease.release();
971
1011
  const tracked = trackBackgroundRun({
972
- sessionId: ctx.sessionId,
1012
+ sessionId: ctx?.sessionId,
973
1013
  label,
974
1014
  cli,
975
1015
  cwd,
@@ -993,9 +1033,16 @@ export function makeDriveAgentTool(runner = defaultRunner, fixedCli, options = {
993
1033
  originClientMessageId: ctx?.originClientMessageId,
994
1034
  });
995
1035
  if ("error" in tracked) {
1036
+ // abort() only STARTS teardown (SIGTERM, grace, SIGKILL); the CLI is
1037
+ // still alive when it returns. Finalizing here would race the live
1038
+ // process for the worktree and git's index.lock, so defer cleanup to
1039
+ // run settlement and consume the abort rejection while we are at it.
1040
+ // The message must not claim a cleanup that has not happened yet.
996
1041
  foregroundAbort.abort();
997
- const lifecycle = safeFinalizeDriveWorktree(managedWorktree);
998
- return appendLifecycleNote(tracked.error, lifecycle);
1042
+ finalizeDriveWorktreeAfter(run, managedWorktree);
1043
+ return managedWorktree
1044
+ ? `${tracked.error}\n\n[worktree lifecycle] Cleaning up ${managedWorktree.session.worktreePath} once ${cliName} exits.`
1045
+ : tracked.error;
999
1046
  }
1000
1047
  ctx?.runYield?.request("background_notification");
1001
1048
  return [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cjhyy/code-shell-capability-coding",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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.9.0"
42
+ "@cjhyy/code-shell-core": "0.9.2"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=20.10"