@gajae-code/coding-agent 0.11.10 → 0.12.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.
Files changed (249) hide show
  1. package/CHANGELOG.md +86 -2
  2. package/dist/types/cli/read-cli.d.ts +1 -0
  3. package/dist/types/cli.d.ts +8 -1
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-profile-activation.d.ts +1 -0
  8. package/dist/types/config/model-profile-contract.d.ts +2 -0
  9. package/dist/types/config/model-registry.d.ts +4 -0
  10. package/dist/types/config/provider-auth-health.d.ts +14 -0
  11. package/dist/types/config/provider-ranking.d.ts +59 -0
  12. package/dist/types/config/settings-schema.d.ts +86 -3
  13. package/dist/types/config/settings.d.ts +3 -0
  14. package/dist/types/extensibility/extensions/types.d.ts +43 -2
  15. package/dist/types/extensibility/shared-events.d.ts +6 -0
  16. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  17. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  18. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  19. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  20. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  21. package/dist/types/gjc-runtime/linux-proc.d.ts +22 -14
  22. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  23. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +112 -1
  24. package/dist/types/gjc-runtime/state-runtime.d.ts +3 -1
  25. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  26. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  27. package/dist/types/gjc-runtime/team-runtime.d.ts +2 -1
  28. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  29. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  30. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +7 -0
  31. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  32. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  33. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  34. package/dist/types/main.d.ts +1 -1
  35. package/dist/types/memories/index.d.ts +2 -0
  36. package/dist/types/modes/acp/acp-agent.d.ts +22 -9
  37. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  38. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  39. package/dist/types/modes/components/queue-pane.d.ts +8 -0
  40. package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
  41. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  42. package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
  43. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  44. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  45. package/dist/types/modes/components/welcome.d.ts +1 -0
  46. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  47. package/dist/types/modes/interactive-mode.d.ts +7 -1
  48. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  49. package/dist/types/modes/types.d.ts +2 -0
  50. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  51. package/dist/types/runtime/memory-domain.d.ts +9 -0
  52. package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
  53. package/dist/types/runtime/memory-guard.d.ts +39 -0
  54. package/dist/types/runtime/memory-limit.d.ts +11 -0
  55. package/dist/types/runtime-mcp/manager.d.ts +8 -0
  56. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  57. package/dist/types/sdk/acp/final-text.d.ts +19 -0
  58. package/dist/types/sdk/acp/mcp.d.ts +23 -0
  59. package/dist/types/sdk/broker/lifecycle.d.ts +7 -0
  60. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -1
  61. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  62. package/dist/types/sdk/bus/index.d.ts +18 -2
  63. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
  64. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  65. package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
  66. package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
  67. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +6 -4
  68. package/dist/types/sdk/bus/telegram-daemon.d.ts +23 -3
  69. package/dist/types/sdk/host/control/operations.d.ts +2 -1
  70. package/dist/types/sdk/host/host.d.ts +2 -0
  71. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  72. package/dist/types/sdk/lifecycle-session.d.ts +10 -1
  73. package/dist/types/sdk/startup-capability.d.ts +6 -0
  74. package/dist/types/session/agent-session.d.ts +51 -19
  75. package/dist/types/session/fallback-chain-controller.d.ts +18 -2
  76. package/dist/types/session/internal/managed-session-scope.d.ts +27 -0
  77. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  78. package/dist/types/session/session-manager.d.ts +59 -13
  79. package/dist/types/session/streaming-output.d.ts +60 -5
  80. package/dist/types/skill-state/active-state.d.ts +0 -4
  81. package/dist/types/skill-state/workflow-hud.d.ts +3 -0
  82. package/dist/types/task/executor.d.ts +6 -0
  83. package/dist/types/task/types.d.ts +36 -0
  84. package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
  85. package/dist/types/tools/browser/launch.d.ts +7 -0
  86. package/dist/types/tools/fetch.d.ts +17 -1
  87. package/dist/types/tools/output-meta.d.ts +21 -1
  88. package/dist/types/tools/read-internals.d.ts +12 -0
  89. package/dist/types/tools/read.d.ts +45 -1
  90. package/dist/types/tools/resource-gc.d.ts +23 -0
  91. package/dist/types/tools/tool-result.d.ts +7 -1
  92. package/package.json +9 -13
  93. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  94. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  95. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  96. package/scripts/run-test-manifest.ts +6 -4
  97. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  98. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  99. package/src/capability/index.ts +8 -0
  100. package/src/cli/args.ts +14 -8
  101. package/src/cli/fast-help.ts +1 -1
  102. package/src/cli/read-cli.ts +2 -1
  103. package/src/cli.ts +49 -2
  104. package/src/commands/deep-interview.ts +29 -9
  105. package/src/commands/harness.ts +35 -12
  106. package/src/commands/read.ts +11 -2
  107. package/src/commands/sdk.ts +91 -7
  108. package/src/commands/team.ts +1 -1
  109. package/src/config/model-profile-activation.ts +1 -0
  110. package/src/config/model-profile-contract.ts +13 -0
  111. package/src/config/model-profiles.ts +24 -0
  112. package/src/config/model-registry.ts +21 -2
  113. package/src/config/provider-auth-health.ts +42 -0
  114. package/src/config/provider-ranking.ts +119 -0
  115. package/src/config/settings-schema.ts +84 -3
  116. package/src/config/settings.ts +254 -108
  117. package/src/defaults/gjc/skills/deep-interview/SKILL.md +229 -82
  118. package/src/defaults/gjc/skills/ralplan/SKILL.md +78 -26
  119. package/src/defaults/gjc/skills/team/SKILL.md +1 -1
  120. package/src/defaults/gjc/skills/ultragoal/SKILL.md +26 -16
  121. package/src/eval/py/executor.ts +7 -2
  122. package/src/extensibility/extensions/runner.ts +4 -0
  123. package/src/extensibility/extensions/types.ts +42 -4
  124. package/src/extensibility/shared-events.ts +5 -0
  125. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -68
  126. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  127. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  128. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  129. package/src/gjc-runtime/deep-interview-state.ts +12 -921
  130. package/src/gjc-runtime/gc-render.ts +1 -0
  131. package/src/gjc-runtime/gc-runtime.ts +11 -1
  132. package/src/gjc-runtime/linux-proc.ts +70 -36
  133. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  134. package/src/gjc-runtime/ralplan-runtime.ts +979 -71
  135. package/src/gjc-runtime/state-runtime.ts +50 -12
  136. package/src/gjc-runtime/state-writer.ts +70 -222
  137. package/src/gjc-runtime/team-launch.ts +34 -5
  138. package/src/gjc-runtime/team-runtime.ts +970 -33
  139. package/src/gjc-runtime/team-store.ts +32 -0
  140. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  141. package/src/gjc-runtime/team-workers.ts +1 -0
  142. package/src/gjc-runtime/tmux-owner-isolation.ts +18 -0
  143. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  144. package/src/gjc-runtime/ultragoal-runtime.ts +13 -7
  145. package/src/gjc-runtime/workflow-manifest.generated.json +97 -350
  146. package/src/gjc-runtime/workflow-manifest.ts +34 -179
  147. package/src/hooks/native-skill-hook.ts +17 -4
  148. package/src/hooks/skill-state.ts +1 -23
  149. package/src/internal-urls/docs-index.generated.ts +10 -11
  150. package/src/internal-urls/local-protocol.ts +23 -4
  151. package/src/internal-urls/local-root-gc.ts +127 -0
  152. package/src/main.ts +9 -5
  153. package/src/memories/index.ts +10 -0
  154. package/src/modes/DESIGN.md +47 -0
  155. package/src/modes/acp/acp-agent.ts +597 -67
  156. package/src/modes/acp/acp-event-mapper.ts +106 -12
  157. package/src/modes/acp/acp-mode.ts +39 -3
  158. package/src/modes/components/assistant-message.ts +123 -22
  159. package/src/modes/components/irc-sidebar.ts +176 -62
  160. package/src/modes/components/model-selector.ts +76 -5
  161. package/src/modes/components/notifications-settings-editor.ts +1 -1
  162. package/src/modes/components/oauth-selector.ts +42 -12
  163. package/src/modes/components/queue-pane.ts +55 -9
  164. package/src/modes/components/queued-message-selector.ts +53 -10
  165. package/src/modes/components/read-tool-group.ts +82 -14
  166. package/src/modes/components/session-observer-overlay.ts +187 -32
  167. package/src/modes/components/settings-selector.ts +60 -18
  168. package/src/modes/components/tool-execution.ts +138 -27
  169. package/src/modes/components/tool-status-header.ts +1 -4
  170. package/src/modes/components/welcome.ts +18 -3
  171. package/src/modes/controllers/command-controller.ts +4 -0
  172. package/src/modes/controllers/event-controller.ts +109 -27
  173. package/src/modes/controllers/extension-ui-controller.ts +23 -12
  174. package/src/modes/controllers/input-controller.ts +62 -6
  175. package/src/modes/controllers/selector-controller.ts +83 -37
  176. package/src/modes/interactive-mode.ts +127 -29
  177. package/src/modes/irc-observation-ledger.ts +71 -15
  178. package/src/modes/runtime-init.ts +20 -2
  179. package/src/modes/types.ts +2 -0
  180. package/src/modes/utils/ui-helpers.ts +94 -1
  181. package/src/prompts/agents/architect.md +9 -1
  182. package/src/prompts/agents/critic.md +9 -0
  183. package/src/prompts/agents/executor.md +1 -1
  184. package/src/prompts/system/system-prompt.md +0 -1
  185. package/src/prompts/tools/read.md +4 -2
  186. package/src/runtime/memory-domain.ts +66 -0
  187. package/src/runtime/memory-guard-contract.ts +73 -0
  188. package/src/runtime/memory-guard.ts +292 -0
  189. package/src/runtime/memory-limit.ts +50 -0
  190. package/src/runtime-mcp/manager.ts +26 -6
  191. package/src/runtime-mcp/smithery-auth.ts +17 -2
  192. package/src/runtime-mcp/smithery-connect.ts +11 -1
  193. package/src/sdk/acp/adapter.ts +8 -1
  194. package/src/sdk/acp/final-text.ts +40 -0
  195. package/src/sdk/acp/mcp.ts +26 -0
  196. package/src/sdk/broker/lifecycle.ts +107 -7
  197. package/src/sdk/bus/chat-daemon-cli.ts +2 -2
  198. package/src/sdk/bus/chat-daemon-control.ts +4 -3
  199. package/src/sdk/bus/config.ts +1 -1
  200. package/src/sdk/bus/control-drain-lease.ts +104 -0
  201. package/src/sdk/bus/index.ts +503 -60
  202. package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
  203. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  204. package/src/sdk/bus/rate-limit-pool.ts +8 -0
  205. package/src/sdk/bus/reconciliation-store.ts +224 -0
  206. package/src/sdk/bus/telegram-daemon-contract.ts +6 -4
  207. package/src/sdk/bus/telegram-daemon.ts +531 -53
  208. package/src/sdk/host/control/dispatch.ts +7 -1
  209. package/src/sdk/host/control/operations.ts +2 -1
  210. package/src/sdk/host/host.ts +25 -3
  211. package/src/sdk/host/query/handlers.ts +55 -0
  212. package/src/sdk/lifecycle-session.ts +19 -3
  213. package/src/sdk/mcp/server.ts +46 -18
  214. package/src/sdk/protocol/operation-inventory.generated.json +76 -0
  215. package/src/sdk/protocol/operation-registry.ts +8 -2
  216. package/src/sdk/session.ts +25 -2
  217. package/src/sdk/startup-capability.ts +6 -0
  218. package/src/session/agent-session.ts +720 -190
  219. package/src/session/fallback-chain-controller.ts +47 -8
  220. package/src/session/internal/managed-session-scope.ts +40 -10
  221. package/src/session/internal/native-publish-outcome.ts +8 -3
  222. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  223. package/src/session/session-manager.ts +1372 -42
  224. package/src/session/streaming-output.ts +262 -43
  225. package/src/setup/provider-onboarding.ts +19 -4
  226. package/src/skill-state/active-state.ts +0 -10
  227. package/src/skill-state/workflow-hud.ts +25 -2
  228. package/src/skill-state/workflow-mutation-guard.ts +65 -7
  229. package/src/task/executor.ts +11 -3
  230. package/src/task/index.ts +2 -0
  231. package/src/task/types.ts +22 -11
  232. package/src/task/ultragoal-redteam-activation.ts +75 -0
  233. package/src/tools/ask.ts +89 -69
  234. package/src/tools/browser/launch.ts +41 -6
  235. package/src/tools/fetch.ts +114 -23
  236. package/src/tools/output-meta.ts +176 -9
  237. package/src/tools/path-utils.ts +5 -1
  238. package/src/tools/read-internals.ts +44 -0
  239. package/src/tools/read.ts +1542 -310
  240. package/src/tools/resource-gc.ts +665 -125
  241. package/src/tools/tool-result.ts +14 -1
  242. package/src/web/search/providers/codex.ts +1 -1
  243. package/src/workflow/workflow-intent-diff.ts +4 -1
  244. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  245. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  246. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  247. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  248. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  249. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
@@ -147,6 +147,17 @@ const MAX_LEGACY_LOCAL_BYTES = 64 * 1024 * 1024;
147
147
 
148
148
  type LegacyMigrationState = "complete" | "cleanup_pending";
149
149
 
150
+ /**
151
+ * Marker values that mean legacy migration has settled for this root and the
152
+ * synchronous resolver may proceed. `cleanup_pending` counts: the entries are
153
+ * installed and content-verified, and only retirement of the legacy source is
154
+ * outstanding. Must stay in sync with {@link readMigrationMarker}, which the
155
+ * async gate uses to decide the same question.
156
+ */
157
+ function isSettledMigrationMarkerValue(value: string): boolean {
158
+ return value === "verified\n" || value === "absent\n" || value === "cleanup_pending\n";
159
+ }
160
+
150
161
  interface LegacyEntrySnapshot {
151
162
  readonly relativePath: string;
152
163
  readonly dev: bigint;
@@ -350,7 +361,7 @@ async function migrateManagedLegacyLocal(
350
361
  throw new Error("Legacy local:// migration destination verification failed");
351
362
  }
352
363
  }
353
- for (const entry of await fs.readdir(staging)) {
364
+ for (const entry of (await fs.readdir(staging)).sort()) {
354
365
  const destination = path.join(localRoot, entry);
355
366
  try {
356
367
  await fs.lstat(destination);
@@ -540,8 +551,13 @@ function initializeLocalRootSyncWhenLegacyAbsent(options: LocalProtocolOptions,
540
551
  if (!rootStat.isDirectory() || rootStat.isSymbolicLink()) throw new Error("Unsafe local:// root");
541
552
  const marker = path.join(localRoot, LEGACY_MIGRATION_MARKER);
542
553
  try {
543
- const value = fsSync.readFileSync(marker, "utf8");
544
- if (value !== "verified\n" && value !== "absent\n") throw new Error("Unsafe local:// migration marker");
554
+ // Accept exactly the marker states the async gate treats as settled. A
555
+ // `cleanup_pending` marker means the entries are fully installed and verified
556
+ // and only retirement of the legacy source is outstanding, so resolution is
557
+ // safe; rejecting it here made the sync resolver fail closed on a root the
558
+ // async gate had already completed.
559
+ if (!isSettledMigrationMarkerValue(fsSync.readFileSync(marker, "utf8")))
560
+ throw new Error("Unsafe local:// migration marker");
545
561
  initializedLocalRoots.add(localRoot);
546
562
  return;
547
563
  } catch (error) {
@@ -558,7 +574,10 @@ function initializeLocalRootSyncWhenLegacyAbsent(options: LocalProtocolOptions,
558
574
  fsSync.writeFileSync(marker, "absent\n", { mode: 0o600, flag: "wx" });
559
575
  } catch (error) {
560
576
  if ((error as NodeJS.ErrnoException).code !== "EEXIST") throw error;
561
- if (fsSync.readFileSync(marker, "utf8") !== "absent\n") throw new Error("Unsafe local:// migration marker");
577
+ // A concurrent initializer won the exclusive create. Any settled marker state
578
+ // it wrote is authoritative; only an unrecognized value is unsafe.
579
+ if (!isSettledMigrationMarkerValue(fsSync.readFileSync(marker, "utf8")))
580
+ throw new Error("Unsafe local:// migration marker");
562
581
  }
563
582
  initializedLocalRoots.add(localRoot);
564
583
  }
@@ -0,0 +1,127 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { isEnoent } from "@gajae-code/utils";
5
+ import type { GcCollectResult, GcContext, GcError, GcRecord, GcStoreAdapter } from "../gjc-runtime/gc-runtime";
6
+
7
+ /**
8
+ * GC for `local://` session roots.
9
+ *
10
+ * `resolveLocalRoot` gives every session its own `<tmp>/gjc-local/<session-id>`
11
+ * directory, and the first `local://` resolution seeds it with a migration
12
+ * marker. Nothing ever removes those directories, so a machine accumulates one
13
+ * per session indefinitely — and the overwhelming majority only ever hold the
14
+ * marker, because most sessions never write a `local://` file.
15
+ *
16
+ * Only marker-only directories are eligible, and only after a grace period.
17
+ * That keeps the rule provably safe without needing a session-liveness oracle:
18
+ * a directory holding real content is never touched, and a session that just
19
+ * started is still inside its grace window.
20
+ */
21
+
22
+ const LOCAL_ROOT_PARENT = "gjc-local";
23
+ const LEGACY_MIGRATION_MARKER = ".gjc-local-legacy-migrated-v1";
24
+
25
+ /** Directories younger than this are left alone so a live session is never raced. */
26
+ const GRACE_MS = 24 * 60 * 60 * 1000;
27
+
28
+ /** Bound the scan so a pathological directory cannot stall `gjc gc`. */
29
+ const MAX_ENTRIES = 20_000;
30
+
31
+ function localRootParent(env: NodeJS.ProcessEnv): string {
32
+ return path.join(env.TMPDIR?.trim() || os.tmpdir(), LOCAL_ROOT_PARENT);
33
+ }
34
+
35
+ /** A root is prunable only when the marker is the single thing inside it. */
36
+ async function classify(dir: string, now: number): Promise<{ status: string; stale: boolean; reason: string } | null> {
37
+ let entries: string[];
38
+ try {
39
+ entries = await fs.readdir(dir);
40
+ } catch (error) {
41
+ if (isEnoent(error)) return null;
42
+ throw error;
43
+ }
44
+
45
+ const extra = entries.filter(name => name !== LEGACY_MIGRATION_MARKER);
46
+ if (extra.length > 0)
47
+ return {
48
+ status: "in_use",
49
+ stale: false,
50
+ reason: `Holds ${extra.length} entr${extra.length === 1 ? "y" : "ies"} beyond the marker`,
51
+ };
52
+
53
+ const stat = await fs.stat(dir);
54
+ if (now - stat.mtimeMs < GRACE_MS) return { status: "recent", stale: false, reason: "Inside the grace window" };
55
+ return { status: "marker_only", stale: true, reason: "Only the migration marker, past the grace window" };
56
+ }
57
+
58
+ export const localRootsGcAdapter: GcStoreAdapter = {
59
+ store: "local_roots",
60
+ async collect(ctx: GcContext): Promise<GcCollectResult> {
61
+ const records: GcRecord[] = [];
62
+ const errors: GcError[] = [];
63
+ const parent = localRootParent(ctx.env);
64
+ const now = Date.now();
65
+
66
+ let dirents: string[];
67
+ try {
68
+ dirents = await fs.readdir(parent);
69
+ } catch (error) {
70
+ if (isEnoent(error)) return { records, errors };
71
+ errors.push({ store: "local_roots", scope: parent, message: (error as Error).message });
72
+ return { records, errors };
73
+ }
74
+
75
+ for (const name of dirents.slice(0, MAX_ENTRIES)) {
76
+ const dir = path.join(parent, name);
77
+ try {
78
+ const classified = await classify(dir, now);
79
+ if (!classified) continue;
80
+ records.push({
81
+ store: "local_roots",
82
+ id: name,
83
+ path: dir,
84
+ status: classified.status,
85
+ stale: classified.stale,
86
+ removable: classified.stale,
87
+ action: classified.stale ? "would_remove" : "none",
88
+ reason: classified.reason,
89
+ });
90
+ } catch (error) {
91
+ errors.push({ store: "local_roots", scope: dir, message: (error as Error).message });
92
+ }
93
+ }
94
+
95
+ if (dirents.length > MAX_ENTRIES) {
96
+ errors.push({
97
+ store: "local_roots",
98
+ scope: parent,
99
+ message: `Scan truncated at ${MAX_ENTRIES} entries`,
100
+ });
101
+ }
102
+
103
+ return { records, errors };
104
+ },
105
+ async prune(record: GcRecord) {
106
+ const dir = record.path;
107
+ if (!dir) return { removed: false, error: "Missing local root path" };
108
+
109
+ // Re-validate immediately before removing: the session may have written a
110
+ // file between collect and prune.
111
+ let classified: Awaited<ReturnType<typeof classify>>;
112
+ try {
113
+ classified = await classify(dir, Date.now());
114
+ } catch (error) {
115
+ return { removed: false, error: (error as Error).message };
116
+ }
117
+ if (!classified) return { removed: false, skipped: "Local root disappeared" };
118
+ if (!classified.stale) return { removed: false, skipped: `Local root no longer eligible (${classified.status})` };
119
+
120
+ try {
121
+ await fs.rm(dir, { recursive: true, force: true });
122
+ return { removed: true };
123
+ } catch (error) {
124
+ return { removed: false, error: (error as Error).message };
125
+ }
126
+ },
127
+ };
package/src/main.ts CHANGED
@@ -698,7 +698,7 @@ async function promptForkSession(session: SessionInfo): Promise<boolean> {
698
698
  }
699
699
  }
700
700
 
701
- async function getChangelogForDisplay(parsed: Args): Promise<string | undefined> {
701
+ export async function getChangelogForDisplay(parsed: Args): Promise<string | undefined> {
702
702
  if (parsed.continue || parsed.resume) {
703
703
  return undefined;
704
704
  }
@@ -710,15 +710,19 @@ async function getChangelogForDisplay(parsed: Args): Promise<string | undefined>
710
710
 
711
711
  const lastVersion = settings.get("lastChangelogVersion");
712
712
  if (!lastVersion) {
713
- settings.set("lastChangelogVersion", VERSION);
714
- await flushChangelogVersion();
713
+ if (settings.canWriteDurableConfig()) {
714
+ settings.set("lastChangelogVersion", VERSION);
715
+ await flushChangelogVersion();
716
+ }
715
717
  return getInstalledVersionChangelogEntry(entries, VERSION)?.content;
716
718
  }
717
719
 
718
720
  if (lastVersion !== VERSION) {
719
721
  const newEntries = getNewEntries(entries, lastVersion);
720
- settings.set("lastChangelogVersion", VERSION);
721
- await flushChangelogVersion();
722
+ if (settings.canWriteDurableConfig()) {
723
+ settings.set("lastChangelogVersion", VERSION);
724
+ await flushChangelogVersion();
725
+ }
722
726
  if (newEntries.length > 0) {
723
727
  return newEntries.map(e => e.content).join("\n\n");
724
728
  }
@@ -976,6 +976,11 @@ function redactSecrets(input: string): string {
976
976
  /(?:sk|pk|rk|tok|key|secret|token|password)[-_A-Za-z0-9]{12,}/g,
977
977
  /[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
978
978
  /(?:AKIA|ASIA)[A-Z0-9]{16}/g,
979
+ // GitHub tokens carry no keyword the first pattern recognizes, so without
980
+ // this they reached MEMORY.md and memory_summary.md verbatim — and the
981
+ // summary is injected into every later session. Same shapes the
982
+ // contribution-prep scrubber already covers.
983
+ /\b(?:gh[opsur]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/g,
979
984
  ];
980
985
  for (const pattern of patterns) {
981
986
  out = out.replace(pattern, "[REDACTED]");
@@ -983,6 +988,11 @@ function redactSecrets(input: string): string {
983
988
  return out;
984
989
  }
985
990
 
991
+ /** Secret scrubbing applied to every phase-2 consolidation output. Test seam. */
992
+ export function redactMemorySecretsForTesting(input: string): string {
993
+ return redactSecrets(input);
994
+ }
995
+
986
996
  function sanitizeSkillName(name: string): string {
987
997
  return name
988
998
  .toLowerCase()
@@ -261,3 +261,50 @@ record both manifest counts as 108 plus CJK review results.
261
261
 
262
262
  No raw third-party design corpus, screenshot, or reference asset is stored by
263
263
  this workflow.
264
+
265
+ ## Sticky transcript viewport contract
266
+
267
+ Live mode remains in natural terminal flow. Entering manual history makes the
268
+ application-owned transcript lane scrollable while the status line and every
269
+ later direct composer child are a fixed suffix at the bottom. PageUp/PageDown
270
+ move by the transcript capacity; the wheel remains three rows. A focused editor
271
+ keeps focus, and ordinary editor input or paste follows live before processing
272
+ that input.
273
+
274
+ Manual output has one bounded boolean indication, exactly **`New output — type
275
+ to follow`**. It is not a count. New visible agent/tool/extension output may set
276
+ it while manual; reflow, transient chrome, reconciliation, and user input may
277
+ not. Following clears it only after a successful live repaint. Manual-era output
278
+ is authoritative in the application transcript but is never retroactively
279
+ replayed into native host scrollback; subsequent ordinary live output follows
280
+ normal host behavior.
281
+
282
+ The transcript is the only selectable coordinate space. Pinned status/composer
283
+ chrome, notices, blank rows, and overlays never enter selection or copied text;
284
+ CJK selection clamping remains directional and grapheme/cell aware. Under
285
+ constrained height the notice drops first, then decorative pet and low-priority
286
+ hooks. Focused editor/cursor, status, and normal hooks outrank those rows; zero
287
+ transcript capacity is valid and must not corrupt cursor geometry.
288
+
289
+ At narrow widths use ANSI-aware terminal-cell measurement. ASCII/no-color keeps
290
+ textual state without SGR. Korean, Japanese, Chinese, and mixed CJK/Latin prose
291
+ must wrap at semantic phrase boundaries, never through an action/status label or
292
+ short identifier; a semantic CJK break is a visual-QA failure.
293
+
294
+ ### Sticky viewport deterministic visual QA
295
+
296
+ `test/fixtures/tui/sticky-viewport-showcase.ts` is a fixed-clock, no-network,
297
+ first-party harness that starts the production `TUI` over a `VirtualTerminal`.
298
+ It constructs transcript, status, hooks, and the real composer as children,
299
+ then drives the live/manual viewport path before capturing the terminal frame.
300
+ Capture with `bun packages/coding-agent/scripts/capture-sticky-viewport-showcase.ts
301
+ --out .gjc/qa/sticky-viewport-<run>` and verify with the paired `--root` script.
302
+ The immutable matrix has exactly 20 keys: `live-overflow`, `manual-history`,
303
+ `manual-new-output`, `multiline-editor-hooks-pet`, `capacity-many`,
304
+ `capacity-one`, `capacity-zero`, and `selection-boundary` at both 80x24 and
305
+ 120x36 Unicode/color; plus `manual-new-output/80x24/ascii-no-color`,
306
+ `capacity-zero/48x10/ascii-no-color`,
307
+ `multiline-editor-hooks-pet/48x10/unicode-color`, and
308
+ `narrow-cjk/48x10/unicode-color`. Do not add or replace a manual-follow case.
309
+
310
+ Each key has only `terminal.txt`, ANSI-preserving `terminal-ansi.txt`, `terminal.html`, and `metadata.json`; the manifest records SHA-256 and byte length. Per-key metadata binds immutable font/render assumptions and the ANSI-aware wrapping/truncation policy. `VirtualTerminal` reconstructs ANSI from visible xterm cells, including cell padding, palette/RGB colors, attributes, and inverse video; plain text is always the stripped reconstruction. The verifier owns an independent literal 20-key oracle and fails closed unless stripped ANSI equals text, `terminal.html` equals the exported canonical `ansiToHtml(terminal-ansi.txt)` byte-for-byte (including its complete document envelope and global CSS), HTML independently preserves the ANSI style-run text, every retained row has the exact `Bun.stringWidth` cell width (including trailing spaces), and `ansi_mode` agrees with required Unicode color SGR or ASCII/no-color output. Every metadata entry has exact CJK phrase-boundary metadata: the narrow-CJK key has only the three canonical boundaries in order and every other key has `[]`. Manual captures prove successful production wheel and PageUp paths and retain observable historical transcript-row evidence. It validates exact payload paths (no duplicates or traversal), immutable source/output revisions, state/status/suffix order, notice cardinality, capacity, actual mouse-copied transcript-only selection, composer, CJK, and provenance invariants. `review-input.json` binds the exact manifest digest, capture author/executor identity, acceptance/design versions, required artifacts, narrow-CJK boundaries, and deterministic host matrix. `--require-independent-review` requires an attestation with an exact root key set; exact per-key result and artifact-check key sets; exact defect `{ description, accepted }` keys with a trimmed, nonblank description; canonical trimmed reviewer identity distinct from both bound identities; the independent-terminal-reviewer role; fixture revision; expected and observed counts of 20; exact checked keys; accepted per-key artifact-check/notes results; accepted artifact/CJK/host decisions; bound digest; and final `accept`. Any malformed, incomplete, or extra attestation content fails closed.