@gajae-code/coding-agent 0.12.1 → 0.12.4

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.
Files changed (66) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/types/config/settings-schema.d.ts +2 -2
  3. package/dist/types/gjc-runtime/ralplan-review-conflicts.d.ts +114 -0
  4. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -3
  5. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +5 -1
  6. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -2
  7. package/dist/types/modes/components/irc-sidebar.d.ts +16 -0
  8. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  9. package/dist/types/modes/controllers/event-controller.d.ts +1 -1
  10. package/dist/types/modes/controllers/extension-ui-controller.d.ts +2 -1
  11. package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
  12. package/dist/types/modes/interactive-mode.d.ts +9 -0
  13. package/dist/types/modes/theme/theme.d.ts +3 -1
  14. package/dist/types/modes/types.d.ts +32 -0
  15. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  16. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +3 -2
  17. package/dist/types/session/agent-session.d.ts +2 -0
  18. package/dist/types/session/internal/managed-session-storage.d.ts +22 -3
  19. package/dist/types/task/index.d.ts +4 -0
  20. package/dist/types/task/output-manager.d.ts +1 -0
  21. package/dist/types/task/receipt.d.ts +2 -0
  22. package/dist/types/tools/index.d.ts +2 -0
  23. package/package.json +8 -8
  24. package/scripts/dogfood-ralplan-review-conflicts.ts +272 -0
  25. package/scripts/generate-sdk-operation-inventory.ts +2 -0
  26. package/scripts/run-acp-conformance.ts +5 -0
  27. package/src/async/job-manager.ts +22 -20
  28. package/src/config/provider-ranking.ts +1 -0
  29. package/src/config/settings-schema.ts +2 -2
  30. package/src/debug/index.ts +42 -17
  31. package/src/defaults/gjc/skills/ralplan/SKILL.md +9 -7
  32. package/src/defaults/gjc/skills/ultragoal/SKILL.md +22 -5
  33. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +2 -1
  34. package/src/gjc-runtime/launch-worktree.ts +103 -1
  35. package/src/gjc-runtime/ledger-event-renderer.ts +1 -0
  36. package/src/gjc-runtime/ralplan-review-conflicts.ts +472 -0
  37. package/src/gjc-runtime/ralplan-runtime.ts +80 -10
  38. package/src/gjc-runtime/ultragoal-evidence.ts +110 -49
  39. package/src/gjc-runtime/ultragoal-runtime.ts +467 -22
  40. package/src/gjc-runtime/workflow-manifest.generated.json +29 -0
  41. package/src/gjc-runtime/workflow-manifest.ts +17 -2
  42. package/src/internal-urls/docs-index.generated.ts +2 -2
  43. package/src/modes/DESIGN.md +53 -0
  44. package/src/modes/components/irc-sidebar.ts +30 -1
  45. package/src/modes/components/tool-status-header.ts +0 -1
  46. package/src/modes/controllers/command-controller.ts +38 -34
  47. package/src/modes/controllers/event-controller.ts +27 -16
  48. package/src/modes/controllers/extension-ui-controller.ts +186 -34
  49. package/src/modes/controllers/selector-controller.ts +115 -22
  50. package/src/modes/interactive-mode.ts +189 -62
  51. package/src/modes/jobs-observer.ts +1 -1
  52. package/src/modes/theme/theme.ts +11 -9
  53. package/src/modes/types.ts +88 -0
  54. package/src/sdk/broker/lifecycle.ts +18 -13
  55. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  56. package/src/sdk/bus/telegram-daemon-contract.ts +3 -2
  57. package/src/sdk/protocol/operation-inventory.generated.json +11 -0
  58. package/src/sdk/session.ts +1 -0
  59. package/src/session/agent-session.ts +22 -1
  60. package/src/session/internal/managed-session-scope.ts +158 -107
  61. package/src/session/internal/managed-session-storage.ts +445 -57
  62. package/src/session/session-storage.ts +29 -44
  63. package/src/task/index.ts +257 -31
  64. package/src/task/output-manager.ts +24 -14
  65. package/src/task/receipt.ts +11 -1
  66. package/src/tools/index.ts +2 -0
@@ -63,6 +63,88 @@ export function canApplyComposerSubmission(
63
63
  ): boolean {
64
64
  return options === undefined || (options.ownsComposer && editor === options.editor);
65
65
  }
66
+
67
+ type PartialActivityStatusContainer = Partial<Pick<Container, "children" | "clear" | "detachChild" | "addChild">>;
68
+
69
+ export function stopInteractiveActivityIndicator(
70
+ ctx: {
71
+ loadingAnimation?: Loader;
72
+ statusContainer?: PartialActivityStatusContainer;
73
+ stopLoadingAnimation?: (options?: { restoreBackground?: boolean }) => void;
74
+ },
75
+ options?: { restoreBackground?: boolean },
76
+ ): void {
77
+ if (ctx.stopLoadingAnimation) {
78
+ ctx.stopLoadingAnimation(options);
79
+ return;
80
+ }
81
+ ctx.loadingAnimation?.stop();
82
+ ctx.loadingAnimation = undefined;
83
+ ctx.statusContainer?.clear?.();
84
+ }
85
+
86
+ export function clearInteractiveActivityLoaders(
87
+ ctx: Pick<
88
+ InteractiveModeContext,
89
+ | "autoCompactionLoader"
90
+ | "autoCompactionEscapeHandler"
91
+ | "retryLoader"
92
+ | "retryCountdownTimer"
93
+ | "retryEscapeHandler"
94
+ | "retryEscapePrimed"
95
+ | "editor"
96
+ >,
97
+ ): void {
98
+ ctx.autoCompactionLoader?.stop();
99
+ ctx.autoCompactionLoader = undefined;
100
+ if (ctx.autoCompactionEscapeHandler) ctx.editor.onEscape = ctx.autoCompactionEscapeHandler;
101
+ ctx.autoCompactionEscapeHandler = undefined;
102
+ ctx.retryLoader?.stop();
103
+ ctx.retryLoader = undefined;
104
+ if (ctx.retryCountdownTimer) clearInterval(ctx.retryCountdownTimer);
105
+ ctx.retryCountdownTimer = undefined;
106
+ if (ctx.retryEscapeHandler) ctx.editor.onEscape = ctx.retryEscapeHandler;
107
+ ctx.retryEscapeHandler = undefined;
108
+ ctx.retryEscapePrimed = false;
109
+ }
110
+
111
+ export function suspendInteractiveActivityIndicator(ctx: {
112
+ loadingAnimation?: Loader;
113
+ statusContainer?: PartialActivityStatusContainer;
114
+ stopLoadingAnimation?: (options?: { restoreBackground?: boolean }) => void;
115
+ syncActivityIndicator?: () => void;
116
+ suspendActivityIndicator?: () => () => void;
117
+ }): () => void {
118
+ if (ctx.suspendActivityIndicator) return ctx.suspendActivityIndicator();
119
+ const suspendedLoader = ctx.loadingAnimation;
120
+ const statusContainer = ctx.statusContainer;
121
+ const canTransferMountedLoader =
122
+ Array.isArray(statusContainer?.children) &&
123
+ typeof statusContainer.detachChild === "function" &&
124
+ typeof statusContainer.addChild === "function";
125
+ const wasMounted =
126
+ canTransferMountedLoader && suspendedLoader !== undefined && statusContainer.children?.includes(suspendedLoader);
127
+ if (suspendedLoader && wasMounted) statusContainer.detachChild?.(suspendedLoader);
128
+ let released = false;
129
+ return () => {
130
+ if (released) return;
131
+ released = true;
132
+ if (
133
+ wasMounted &&
134
+ suspendedLoader &&
135
+ ctx.loadingAnimation === suspendedLoader &&
136
+ Array.isArray(statusContainer?.children) &&
137
+ !statusContainer.children.includes(suspendedLoader)
138
+ ) {
139
+ statusContainer.addChild?.(suspendedLoader);
140
+ }
141
+ syncInteractiveActivityIndicator(ctx);
142
+ };
143
+ }
144
+
145
+ export function syncInteractiveActivityIndicator(ctx: { syncActivityIndicator?: () => void }): void {
146
+ ctx.syncActivityIndicator?.();
147
+ }
66
148
  export type TodoStatus = "pending" | "in_progress" | "completed" | "abandoned";
67
149
 
68
150
  export type TodoItem = {
@@ -164,6 +246,8 @@ export interface InteractiveModeContext {
164
246
  init(): Promise<void>;
165
247
  shutdown(): Promise<void>;
166
248
  checkShutdownRequested(): Promise<void>;
249
+ isStopped?(): boolean;
250
+ onStop(callback: () => void): () => void;
167
251
 
168
252
  // Extension UI integration
169
253
  setToolUIContext(uiContext: ExtensionUIContext, hasUI: boolean): void;
@@ -190,6 +274,9 @@ export interface InteractiveModeContext {
190
274
  setWorkingMessage(message?: string): void;
191
275
  applyPendingWorkingMessage(): void;
192
276
  ensureLoadingAnimation(): void;
277
+ syncActivityIndicator(): void;
278
+ suspendActivityIndicator(): () => void;
279
+ stopLoadingAnimation(options?: { restoreBackground?: boolean }): void;
193
280
  /**
194
281
  * Commit a pet mode through the shared result-returning policy: capability
195
282
  * is rechecked immediately before mutation and the preference persists only
@@ -253,6 +340,7 @@ export interface InteractiveModeContext {
253
340
  updateEditorTopBorder(): void;
254
341
  updateEditorBorderColor(): void;
255
342
  rebuildChatFromMessages(policy: TranscriptRebuildPolicy): void;
343
+ updateEditorChrome(): void;
256
344
  setTodos(todos: TodoItem[] | TodoPhase[]): void;
257
345
  reloadTodos(): Promise<void>;
258
346
  toggleTodoExpansion(): void;
@@ -3260,26 +3260,31 @@ async function executeLifecycleResponse(
3260
3260
  `Unable to delete saved session artifacts: ${error instanceof Error ? error.message : String(error)}`,
3261
3261
  );
3262
3262
  }
3263
- const retainedArtifactsCleanup =
3263
+ const retainedArtifactsRootOnly =
3264
3264
  deleted.kind === "cleanup_pending" &&
3265
3265
  deleted.phase === "artifacts" &&
3266
3266
  deleted.detachedArtifactsPath !== undefined &&
3267
- cleanupTarget.plannedArtifactsPath !== undefined &&
3268
- (deleted.detachedArtifactsPath === cleanupTarget.plannedArtifactsPath ||
3269
- deleted.detachedArtifactsPath === `${cleanupTarget.plannedArtifactsPath}.removing`);
3270
- if (deleted.kind === "artifacts_removed" || retainedArtifactsCleanup) {
3267
+ (() => {
3268
+ const snapshot = native.snapshotDirectoryTree(deleted.detachedArtifactsPath);
3269
+ if (!snapshot.ok || !snapshot.snapshot) return false;
3270
+ const entries = snapshot.snapshot.entries;
3271
+ return entries.length === 1 && entries[0]?.relativePath === "" && entries[0].kind === "directory";
3272
+ })();
3273
+ const retainedArtifactsEvidence =
3274
+ retainedArtifactsRootOnly && deleted.kind === "cleanup_pending" && deleted.phase === "artifacts"
3275
+ ? {
3276
+ detachedArtifactsPath: deleted.detachedArtifactsPath,
3277
+ ...(deleted.artifactsIdentity
3278
+ ? { artifactsIdentity: serializeCleanupIdentity(deleted.artifactsIdentity) }
3279
+ : {}),
3280
+ }
3281
+ : {};
3282
+ if (deleted.kind === "artifacts_removed" || retainedArtifactsRootOnly) {
3271
3283
  const transcriptPhaseCleanup = {
3272
3284
  ...preauthorizedCleanup,
3273
3285
  phase: "transcript" as const,
3274
3286
  artifactsRemoved: true,
3275
- ...(deleted.kind === "cleanup_pending" && deleted.phase === "artifacts"
3276
- ? {
3277
- detachedArtifactsPath: deleted.detachedArtifactsPath,
3278
- ...(deleted.artifactsIdentity
3279
- ? { artifactsIdentity: serializeCleanupIdentity(deleted.artifactsIdentity) }
3280
- : {}),
3281
- }
3282
- : {}),
3287
+ ...(retainedArtifactsRootOnly ? retainedArtifactsEvidence : {}),
3283
3288
  ...(preauthorizedCleanup.artifactTree
3284
3289
  ? { artifactTree: { ...preauthorizedCleanup.artifactTree, completed: true as const } }
3285
3290
  : {}),
@@ -38,11 +38,12 @@ export type ChatDaemonAction = "stop" | "reload";
38
38
  * chat daemons after notification configuration parsing changes. Generation 15
39
39
  * applies the current notification configuration directly when starting chat
40
40
  * daemon transports. Generation 16 applies Telegram sound-policy configuration
41
- * through shared notification parsing.
41
+ * through shared notification parsing. Generation 17 binds managed-session
42
+ * replacement to exact native filesystem authority.
42
43
  */
43
44
  export const CHAT_DAEMON_GENERATIONS: Readonly<Record<ChatDaemonKind, number>> = {
44
- discord: 16,
45
- slack: 16,
45
+ discord: 17,
46
+ slack: 17,
46
47
  };
47
48
 
48
49
  export function chatDaemonGeneration(kind: ChatDaemonKind): number {
@@ -46,9 +46,10 @@ export const NOTIFICATION_PROTOCOL_VERSION = 3;
46
46
  * 33 adds action-bound multi-select state rendering and replay-safe option
47
47
  * snapshots. Generation 34 converts non-photo image formats (including WebP)
48
48
  * into Telegram-compatible photo uploads when possible. Generation 35 adds
49
- * user-created topic adoption (forum-topic folder picker).
49
+ * user-created topic adoption (forum-topic folder picker). Generation 36 binds
50
+ * managed-session replacement to exact native filesystem authority.
50
51
  */
51
- export const DAEMON_GENERATION = 35;
52
+ export const DAEMON_GENERATION = 36;
52
53
 
53
54
  /**
54
55
  * Serving-compatibility boundary for daemon lifecycle requests. Epoch 1 covers
@@ -2531,6 +2531,17 @@
2531
2531
  "testIds": "not_applicable"
2532
2532
  }
2533
2533
  },
2534
+ {
2535
+ "sourceId": "agent_session:registerToolSessionCleanup",
2536
+ "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
2537
+ "sourceKind": "agent_session",
2538
+ "decision": "exclude",
2539
+ "rationale": "internal tool lifecycle cleanup registration, not a user-facing SDK control seam",
2540
+ "exclusionMetadata": {
2541
+ "adapterMappings": "not_applicable",
2542
+ "testIds": "not_applicable"
2543
+ }
2544
+ },
2534
2545
  {
2535
2546
  "sourceId": "agent_session:getAsyncJobSnapshot",
2536
2547
  "sourceFile": "packages/coding-agent/src/session/agent-session.ts",
@@ -1623,6 +1623,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1623
1623
  }
1624
1624
  },
1625
1625
  getArtifactManager: () => sessionManager.getArtifactManager(),
1626
+ registerSessionCleanup: cleanup => session?.registerToolSessionCleanup(cleanup) ?? (() => {}),
1626
1627
  settings,
1627
1628
  authStorage,
1628
1629
  modelRegistry,
@@ -1884,6 +1884,7 @@ export class AgentSession {
1884
1884
  #providerCacheSessionId: string | undefined;
1885
1885
  #isDisposed = false;
1886
1886
  #disposePromise: Promise<void> | undefined;
1887
+ readonly #toolSessionCleanups = new Set<() => Promise<void> | void>();
1887
1888
  #newSessionTransition: Promise<boolean> | undefined;
1888
1889
  // Extension system
1889
1890
  #extensionRunner: ExtensionRunner | undefined = undefined;
@@ -3233,6 +3234,21 @@ export class AgentSession {
3233
3234
  return this.#agentId;
3234
3235
  }
3235
3236
 
3237
+ registerToolSessionCleanup(cleanup: () => Promise<void> | void): () => void {
3238
+ if (this.#isDisposed) throw new Error("Cannot register tool cleanup after session disposal has started.");
3239
+ this.#toolSessionCleanups.add(cleanup);
3240
+ return () => this.#toolSessionCleanups.delete(cleanup);
3241
+ }
3242
+
3243
+ async #runToolSessionCleanups(): Promise<void> {
3244
+ const cleanups = Array.from(this.#toolSessionCleanups);
3245
+ this.#toolSessionCleanups.clear();
3246
+ const results = await Promise.allSettled(cleanups.map(async cleanup => await cleanup()));
3247
+ for (const result of results) {
3248
+ if (result.status === "rejected") logger.warn("Tool session cleanup failed", { error: String(result.reason) });
3249
+ }
3250
+ }
3251
+
3236
3252
  getAsyncJobSnapshot(options?: { recentLimit?: number }): AsyncJobSnapshot | null {
3237
3253
  const manager = AsyncJobManager.instance();
3238
3254
  if (!manager) return null;
@@ -5565,6 +5581,7 @@ export class AgentSession {
5565
5581
  AsyncJobManager.setInstance(undefined);
5566
5582
  }
5567
5583
  }
5584
+ await this.#runToolSessionCleanups();
5568
5585
  // Only disconnect the MCP manager THIS session owns (top-level sessions that
5569
5586
  // connected plugin-bundle MCP servers). Subagents and callers that merely
5570
5587
  // observe the process-global manager must never tear down a manager they do
@@ -15703,7 +15720,10 @@ export class AgentSession {
15703
15720
  */
15704
15721
  async switchSession(
15705
15722
  sessionPath: string,
15706
- options?: { transition?: SessionSwitchEvent["transition"] },
15723
+ options?: {
15724
+ transition?: SessionSwitchEvent["transition"];
15725
+ onTransitionMutationStarted?: () => void;
15726
+ },
15707
15727
  ): Promise<boolean> {
15708
15728
  this.#beginSessionTransition("switch-session");
15709
15729
  try {
@@ -15723,6 +15743,7 @@ export class AgentSession {
15723
15743
  return false;
15724
15744
  }
15725
15745
  }
15746
+ options?.onTransitionMutationStarted?.();
15726
15747
 
15727
15748
  this.#disconnectFromAgent();
15728
15749
  await this.abort();