@gajae-code/coding-agent 0.11.6 → 0.11.8

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 (259) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/dist/types/capability/index.d.ts +10 -20
  3. package/dist/types/capability/types.d.ts +3 -0
  4. package/dist/types/cli/args.d.ts +0 -2
  5. package/dist/types/commands/daemon.d.ts +2 -2
  6. package/dist/types/commands/sdk.d.ts +20 -2
  7. package/dist/types/config/keybindings.d.ts +34 -4
  8. package/dist/types/config/model-profile-activation.d.ts +4 -3
  9. package/dist/types/config/model-profile-contract.d.ts +38 -0
  10. package/dist/types/config/settings-schema.d.ts +70 -7
  11. package/dist/types/edit/modes/patch.d.ts +13 -0
  12. package/dist/types/edit/path-mutation-lock.d.ts +15 -0
  13. package/dist/types/eval/py/executor.d.ts +1 -1
  14. package/dist/types/eval/py/runner-artifact.d.ts +8 -0
  15. package/dist/types/eval/py/tool-bridge.d.ts +1 -2
  16. package/dist/types/gjc-runtime/repository-binding.d.ts +63 -0
  17. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +6 -5
  18. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +20 -0
  19. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +43 -5
  20. package/dist/types/main.d.ts +32 -1
  21. package/dist/types/modes/components/btw-panel.d.ts +1 -0
  22. package/dist/types/modes/components/model-selector.d.ts +2 -0
  23. package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
  24. package/dist/types/modes/components/tool-status-header.d.ts +4 -2
  25. package/dist/types/modes/components/welcome.d.ts +2 -0
  26. package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
  27. package/dist/types/modes/controllers/command-controller.d.ts +2 -0
  28. package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
  29. package/dist/types/modes/interactive-mode.d.ts +7 -2
  30. package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
  31. package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
  32. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
  33. package/dist/types/modes/types.d.ts +4 -0
  34. package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
  35. package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
  36. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  37. package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
  38. package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
  39. package/dist/types/sdk/broker/broker.d.ts +2 -0
  40. package/dist/types/sdk/broker/lifecycle.d.ts +6 -0
  41. package/dist/types/sdk/bus/chat-daemon-control.d.ts +6 -1
  42. package/dist/types/sdk/bus/config-commands.d.ts +2 -2
  43. package/dist/types/sdk/bus/config.d.ts +2 -0
  44. package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
  45. package/dist/types/sdk/bus/index.d.ts +26 -1
  46. package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
  47. package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
  48. package/dist/types/sdk/bus/notification-service.d.ts +34 -0
  49. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +105 -0
  50. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +18 -3
  51. package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
  52. package/dist/types/sdk/bus/telegram-daemon.d.ts +189 -4
  53. package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
  54. package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
  55. package/dist/types/sdk/client/discovery.d.ts +2 -0
  56. package/dist/types/sdk/client/liveness.d.ts +7 -0
  57. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  58. package/dist/types/sdk/host/host.d.ts +1 -1
  59. package/dist/types/sdk/host/query/handlers.d.ts +9 -0
  60. package/dist/types/sdk/index.d.ts +2 -0
  61. package/dist/types/sdk/prompt-status.d.ts +88 -0
  62. package/dist/types/sdk/session-directory.d.ts +1 -1
  63. package/dist/types/sdk/startup-capability.d.ts +3 -0
  64. package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
  65. package/dist/types/sdk/transport/index.d.ts +12 -0
  66. package/dist/types/sdk/transport/relay.d.ts +43 -0
  67. package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
  68. package/dist/types/sdk/transport/socket.d.ts +7 -0
  69. package/dist/types/sdk/transport/stdio.d.ts +3 -0
  70. package/dist/types/session/agent-session.d.ts +30 -5
  71. package/dist/types/session/btw-contract.d.ts +16 -0
  72. package/dist/types/session/internal/managed-session-scope.d.ts +32 -9
  73. package/dist/types/session/internal/managed-session-storage.d.ts +27 -10
  74. package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
  75. package/dist/types/session/session-manager.d.ts +25 -4
  76. package/dist/types/session/session-storage.d.ts +18 -0
  77. package/dist/types/setup/credential-import.d.ts +5 -1
  78. package/dist/types/setup/provider-onboarding.d.ts +2 -0
  79. package/dist/types/ssh/utils.d.ts +1 -0
  80. package/dist/types/task/discovery.d.ts +3 -1
  81. package/dist/types/task/index.d.ts +3 -1
  82. package/dist/types/task/receipt.d.ts +2 -0
  83. package/dist/types/task/types.d.ts +94 -0
  84. package/dist/types/tools/ask.d.ts +454 -174
  85. package/dist/types/tools/fetch.d.ts +5 -8
  86. package/dist/types/tools/image-gen.d.ts +63 -1
  87. package/dist/types/tools/index.d.ts +4 -0
  88. package/dist/types/tools/skill-discovery.d.ts +7 -0
  89. package/dist/types/tools/sqlite-reader.d.ts +7 -0
  90. package/dist/types/utils/prompt-suggestion.d.ts +33 -0
  91. package/dist/types/utils/shell-snapshot.d.ts +12 -0
  92. package/dist/types/web/insane/bridge.d.ts +1 -0
  93. package/dist/types/web/insane/url-guard.d.ts +17 -0
  94. package/package.json +8 -8
  95. package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
  96. package/scripts/dogfood-repository-binding.ts +209 -0
  97. package/scripts/generate-sdk-operation-inventory.ts +4 -0
  98. package/scripts/run-test-manifest.ts +4 -4
  99. package/src/capability/index.ts +50 -42
  100. package/src/capability/types.ts +3 -0
  101. package/src/cli/args.ts +2 -92
  102. package/src/cli/notify-cli.ts +5 -38
  103. package/src/cli/setup-cli.ts +10 -4
  104. package/src/cli.ts +52 -9
  105. package/src/commands/daemon.ts +22 -5
  106. package/src/commands/sdk.ts +44 -11
  107. package/src/config/keybindings.ts +158 -25
  108. package/src/config/model-profile-activation.ts +7 -18
  109. package/src/config/model-profile-contract.ts +161 -0
  110. package/src/config/model-profiles.ts +18 -0
  111. package/src/config/model-registry.ts +11 -1
  112. package/src/config/settings-schema.ts +73 -7
  113. package/src/coordinator-mcp/model-preset.ts +21 -74
  114. package/src/defaults/gjc/skills/deep-interview/SKILL.md +2 -2
  115. package/src/defaults/gjc/skills/ralplan/SKILL.md +3 -1
  116. package/src/defaults/gjc/skills/ultragoal/SKILL.md +42 -1
  117. package/src/discovery/ssh.ts +12 -1
  118. package/src/edit/index.ts +3 -6
  119. package/src/edit/modes/patch.ts +38 -1
  120. package/src/edit/modes/replace.ts +42 -0
  121. package/src/edit/path-mutation-lock.ts +112 -0
  122. package/src/eval/py/executor.ts +30 -11
  123. package/src/eval/py/kernel.ts +1 -21
  124. package/src/eval/py/prelude.py +2 -2
  125. package/src/eval/py/runner-artifact.ts +84 -0
  126. package/src/eval/py/tool-bridge.ts +34 -18
  127. package/src/extensibility/gjc-plugins/runtime-adapters.ts +2 -1
  128. package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
  129. package/src/gjc-runtime/launch-tmux.ts +1 -1
  130. package/src/gjc-runtime/managed-owner-supervisor.ts +83 -4
  131. package/src/gjc-runtime/ralplan-runtime.ts +75 -3
  132. package/src/gjc-runtime/repository-binding.ts +267 -0
  133. package/src/gjc-runtime/session-state-sidecar.ts +25 -9
  134. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -1
  135. package/src/gjc-runtime/tmux-sessions.ts +45 -6
  136. package/src/gjc-runtime/ultragoal-guard.ts +93 -15
  137. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +103 -0
  138. package/src/gjc-runtime/ultragoal-runtime.ts +456 -31
  139. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  140. package/src/gjc-runtime/workflow-manifest.ts +23 -0
  141. package/src/internal-urls/docs-index.generated.ts +12 -10
  142. package/src/internal-urls/local-protocol.ts +64 -21
  143. package/src/internal-urls/mcp-protocol.ts +73 -11
  144. package/src/main.ts +111 -37
  145. package/src/modes/DESIGN.md +5 -0
  146. package/src/modes/acp/acp-agent.ts +50 -16
  147. package/src/modes/action-registry.ts +1 -1
  148. package/src/modes/components/btw-panel.ts +82 -16
  149. package/src/modes/components/model-selector.ts +73 -11
  150. package/src/modes/components/plan-preview-overlay.ts +1 -1
  151. package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
  152. package/src/modes/components/tool-status-header.ts +25 -4
  153. package/src/modes/components/welcome.ts +22 -18
  154. package/src/modes/controllers/btw-controller.ts +119 -36
  155. package/src/modes/controllers/command-controller.ts +97 -54
  156. package/src/modes/controllers/event-controller.ts +10 -0
  157. package/src/modes/controllers/input-controller.ts +38 -2
  158. package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
  159. package/src/modes/controllers/selector-controller.ts +149 -63
  160. package/src/modes/interactive-mode.ts +57 -34
  161. package/src/modes/prompt-action-autocomplete.ts +32 -13
  162. package/src/modes/prompt-suggestion-controller.ts +96 -0
  163. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
  164. package/src/modes/types.ts +4 -0
  165. package/src/modes/utils/hotkeys-markdown.ts +64 -36
  166. package/src/modes/utils/ui-helpers.ts +5 -3
  167. package/src/prompts/agents/architect.md +0 -1
  168. package/src/prompts/agents/critic.md +0 -1
  169. package/src/prompts/system/btw-user.md +3 -8
  170. package/src/prompts/system/prompt-suggestion-system.md +32 -0
  171. package/src/prompts/system/system-prompt.md +6 -4
  172. package/src/prompts/tools/cron.md +3 -1
  173. package/src/prompts/tools/skill-discovery.md +1 -0
  174. package/src/runtime-mcp/client.ts +41 -55
  175. package/src/runtime-mcp/content-limits.ts +71 -0
  176. package/src/runtime-mcp/json-rpc.ts +23 -9
  177. package/src/runtime-mcp/manager.ts +51 -53
  178. package/src/runtime-mcp/oauth-flow.ts +0 -72
  179. package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
  180. package/src/runtime-mcp/transports/http.ts +63 -22
  181. package/src/sdk/broker/broker.ts +34 -20
  182. package/src/sdk/broker/lifecycle.ts +185 -36
  183. package/src/sdk/bus/chat-daemon-control.ts +19 -5
  184. package/src/sdk/bus/config-commands.ts +2 -2
  185. package/src/sdk/bus/config.ts +9 -1
  186. package/src/sdk/bus/conversation-store.ts +40 -6
  187. package/src/sdk/bus/index.ts +244 -70
  188. package/src/sdk/bus/lifecycle-commands.ts +63 -10
  189. package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
  190. package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
  191. package/src/sdk/bus/notification-orchestration.ts +12 -0
  192. package/src/sdk/bus/notification-service.ts +68 -20
  193. package/src/sdk/bus/prompt-reconciliation.ts +239 -0
  194. package/src/sdk/bus/recent-activity.ts +90 -26
  195. package/src/sdk/bus/telegram-daemon-contract.ts +18 -3
  196. package/src/sdk/bus/telegram-daemon-control.ts +147 -35
  197. package/src/sdk/bus/telegram-daemon.ts +2463 -450
  198. package/src/sdk/bus/telegram-reference.ts +20 -2
  199. package/src/sdk/bus/topic-registry.ts +451 -12
  200. package/src/sdk/cli/session-cli.ts +15 -2
  201. package/src/sdk/client/discovery.ts +19 -3
  202. package/src/sdk/client/liveness.ts +49 -0
  203. package/src/sdk/host/control/dispatch.ts +1 -1
  204. package/src/sdk/host/control/operations.ts +1 -1
  205. package/src/sdk/host/host.ts +2 -6
  206. package/src/sdk/host/query/handlers.ts +70 -5
  207. package/src/sdk/index.ts +12 -0
  208. package/src/sdk/prompt-status.ts +97 -0
  209. package/src/sdk/protocol/operation-inventory.generated.json +58 -0
  210. package/src/sdk/protocol/operation-registry.ts +18 -4
  211. package/src/sdk/session-directory.ts +5 -1
  212. package/src/sdk/session.ts +31 -5
  213. package/src/sdk/startup-capability.ts +9 -1
  214. package/src/sdk/transport/auth-preface.ts +82 -0
  215. package/src/sdk/transport/index.ts +18 -0
  216. package/src/sdk/transport/relay.ts +252 -0
  217. package/src/sdk/transport/serve-cli.ts +105 -0
  218. package/src/sdk/transport/socket.ts +197 -0
  219. package/src/sdk/transport/stdio.ts +17 -0
  220. package/src/session/agent-session.ts +292 -154
  221. package/src/session/blob-store.ts +25 -7
  222. package/src/session/btw-contract.ts +59 -0
  223. package/src/session/internal/managed-session-scope.ts +1163 -137
  224. package/src/session/internal/managed-session-storage.ts +397 -234
  225. package/src/session/internal/native-publish-outcome.ts +266 -0
  226. package/src/session/session-manager.ts +620 -403
  227. package/src/session/session-storage.ts +128 -11
  228. package/src/setup/credential-import.ts +28 -3
  229. package/src/setup/provider-onboarding.ts +30 -1
  230. package/src/setup/provider-presets.json +2 -1
  231. package/src/slash-commands/builtin-registry.ts +1 -1
  232. package/src/ssh/utils.ts +14 -0
  233. package/src/task/discovery.ts +8 -2
  234. package/src/task/gjc-command.ts +2 -2
  235. package/src/task/index.ts +121 -15
  236. package/src/task/receipt.ts +3 -0
  237. package/src/task/types.ts +34 -3
  238. package/src/tools/ask.ts +199 -89
  239. package/src/tools/ast-edit.ts +2 -2
  240. package/src/tools/bash-pty-selection.ts +2 -2
  241. package/src/tools/browser/screenshot-format.ts +6 -1
  242. package/src/tools/cron.ts +1 -1
  243. package/src/tools/fetch.ts +13 -131
  244. package/src/tools/image-gen.ts +476 -23
  245. package/src/tools/index.ts +4 -0
  246. package/src/tools/read.ts +2 -0
  247. package/src/tools/render-utils.ts +7 -1
  248. package/src/tools/skill-discovery.ts +39 -3
  249. package/src/tools/sqlite-reader.ts +63 -5
  250. package/src/utils/edit-mode.ts +2 -2
  251. package/src/utils/pasted-image-path.ts +15 -6
  252. package/src/utils/prompt-suggestion.ts +265 -0
  253. package/src/utils/shell-snapshot.ts +173 -63
  254. package/src/utils/sixel.ts +3 -3
  255. package/src/web/insane/bridge.ts +5 -1
  256. package/src/web/insane/url-guard.ts +140 -27
  257. package/src/web/scrapers/docs-rs.ts +30 -5
  258. package/src/web/scrapers/types.ts +19 -49
  259. package/src/web/scrapers/utils.ts +12 -26
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Product-surface dogfood for #2901 repository binding.
3
+ *
4
+ * Creates two sibling git repos, runs real `gjc ultragoal` / `gjc ralplan`
5
+ * CLI entrypoints from source, and prints fail-closed mismatch evidence.
6
+ *
7
+ * Usage (from monorepo root):
8
+ * bun packages/coding-agent/scripts/dogfood-repository-binding.ts
9
+ */
10
+ import * as fsp from "node:fs/promises";
11
+ import * as os from "node:os";
12
+ import * as path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import {
15
+ assertCwdMatchesRepositoryBinding,
16
+ assertPathUnderRepositoryBinding,
17
+ parseRepositoryBinding,
18
+ resolveTaskRepositoryBinding,
19
+ } from "../src/gjc-runtime/repository-binding";
20
+ import { readUltragoalPlan, startNextUltragoalGoal } from "../src/gjc-runtime/ultragoal-runtime";
21
+
22
+ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../..");
23
+ const cli = path.join(repoRoot, "packages/coding-agent/src/cli.ts");
24
+
25
+ async function runGit(cwd: string, args: string[]): Promise<void> {
26
+ const proc = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
27
+ const code = await proc.exited;
28
+ if (code !== 0) throw new Error(`git ${args.join(" ")} failed: ${await new Response(proc.stderr).text()}`);
29
+ }
30
+
31
+ async function initRepo(root: string): Promise<void> {
32
+ await fsp.mkdir(root, { recursive: true });
33
+ await runGit(root, ["init"]);
34
+ await runGit(root, ["config", "user.email", "dogfood@example.com"]);
35
+ await runGit(root, ["config", "user.name", "Dogfood"]);
36
+ await fsp.writeFile(path.join(root, "README.md"), `repo ${path.basename(root)}\n`);
37
+ await runGit(root, ["add", "README.md"]);
38
+ await runGit(root, ["commit", "-m", `init ${path.basename(root)}`]);
39
+ }
40
+
41
+ async function runCli(
42
+ cwd: string,
43
+ args: string[],
44
+ env: NodeJS.ProcessEnv,
45
+ ): Promise<{ code: number; stdout: string; stderr: string }> {
46
+ const proc = Bun.spawn(["bun", cli, ...args], { cwd, env, stdout: "pipe", stderr: "pipe" });
47
+ const [code, stdout, stderr] = await Promise.all([
48
+ proc.exited,
49
+ new Response(proc.stdout).text(),
50
+ new Response(proc.stderr).text(),
51
+ ]);
52
+ return { code, stdout, stderr };
53
+ }
54
+
55
+ async function main(): Promise<void> {
56
+ const dogfoodRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "gjc-dogfood-2901-"));
57
+ const left = path.join(dogfoodRoot, "gajae-code");
58
+ const right = path.join(dogfoodRoot, "oh-my-openagent-senpi");
59
+ await initRepo(left);
60
+ await initRepo(right);
61
+
62
+ const sessionId = `dogfood-2901-${process.pid}`;
63
+ const env = { ...process.env, GJC_SESSION_ID: sessionId };
64
+
65
+ console.log("# Dogfood: repository binding (#2901)");
66
+ console.log(`root=${dogfoodRoot}`);
67
+ console.log(`left=${left}`);
68
+ console.log(`right=${right}`);
69
+ console.log(`session=${sessionId}`);
70
+ console.log(`cli=${cli}`);
71
+ console.log(`bun=${Bun.version}`);
72
+ console.log(
73
+ `commit=${Bun.spawnSync(["git", "-C", repoRoot, "rev-parse", "--short", "HEAD"]).stdout.toString().trim()}`,
74
+ );
75
+ console.log();
76
+
77
+ // 1) Product CLI: create goals stamps binding
78
+ const create = await runCli(
79
+ left,
80
+ [
81
+ "ultragoal",
82
+ "create-goals",
83
+ "--brief",
84
+ "@goal dogfood binding\nProve repository binding stamps and fail-closed sibling mismatch.",
85
+ "--json",
86
+ ],
87
+ env,
88
+ );
89
+ console.log("## 1) gjc ultragoal create-goals (LEFT)");
90
+ console.log(`exit=${create.code}`);
91
+ console.log(create.stdout.trim() || create.stderr.trim());
92
+ if (create.code !== 0) process.exit(1);
93
+
94
+ const plan = await readUltragoalPlan(left, sessionId);
95
+ if (!plan?.repositoryBinding) {
96
+ console.error("FAIL: plan missing repositoryBinding");
97
+ process.exit(1);
98
+ }
99
+ console.log();
100
+ console.log("## 2) goals.json repositoryBinding");
101
+ console.log(JSON.stringify(plan.repositoryBinding, null, 2));
102
+
103
+ // 2) Matching cwd starts goal
104
+ console.log();
105
+ console.log("## 3) startNextUltragoalGoal on LEFT (match)");
106
+ const started = await startNextUltragoalGoal({ cwd: left, sessionId });
107
+ console.log(`ok goal=${started.goal?.id} status=${started.goal?.status}`);
108
+
109
+ // 3) Sibling mismatch fails closed
110
+ console.log();
111
+ console.log("## 4) assertCwdMatchesRepositoryBinding on RIGHT (sibling)");
112
+ try {
113
+ await assertCwdMatchesRepositoryBinding(right, plan.repositoryBinding);
114
+ console.error("FAIL: expected identity_mismatch");
115
+ process.exit(1);
116
+ } catch (error) {
117
+ console.log(`fail_closed: ${error instanceof Error ? error.message : String(error)}`);
118
+ }
119
+
120
+ // 4) Path escape fails closed
121
+ console.log();
122
+ console.log("## 5) assertPathUnderRepositoryBinding sibling absolute path");
123
+ try {
124
+ assertPathUnderRepositoryBinding(plan.repositoryBinding, path.join(right, "README.md"));
125
+ console.error("FAIL: expected path_outside_root");
126
+ process.exit(1);
127
+ } catch (error) {
128
+ console.log(`fail_closed: ${error instanceof Error ? error.message : String(error)}`);
129
+ }
130
+
131
+ // 5) Task binding stamp + sibling fail-closed (pre-discovery authority)
132
+ console.log();
133
+ console.log("## 6) task binding stamp (omit declaration) + sibling reject");
134
+ const stamped = await resolveTaskRepositoryBinding(left, undefined);
135
+ console.log(`stamped_worktreeRoot=${stamped.worktreeRoot}`);
136
+ await assertCwdMatchesRepositoryBinding(left, stamped);
137
+ console.log("task_stamp_ok on LEFT");
138
+ const taskBinding = parseRepositoryBinding(plan.repositoryBinding);
139
+ try {
140
+ await resolveTaskRepositoryBinding(right, taskBinding);
141
+ console.error("FAIL: task binding should reject RIGHT");
142
+ process.exit(1);
143
+ } catch (error) {
144
+ console.log(`task_binding_fail_closed on RIGHT: ${error instanceof Error ? error.message : String(error)}`);
145
+ }
146
+
147
+ // 6) Ralplan seed stamps binding via product CLI
148
+ console.log();
149
+ console.log("## 7) gjc ralplan seed (LEFT)");
150
+ const ralplan = await runCli(left, ["ralplan", "--json", "dogfood multi-repo binding"], env);
151
+ console.log(`exit=${ralplan.code}`);
152
+ console.log(ralplan.stdout.trim() || ralplan.stderr.trim());
153
+ if (ralplan.code !== 0) process.exit(1);
154
+ const statePath = path.join(left, ".gjc", `_session-${sessionId}`, "state", "ralplan-state.json");
155
+ const state = JSON.parse(await fsp.readFile(statePath, "utf8")) as {
156
+ repository_binding?: unknown;
157
+ run_id?: string;
158
+ };
159
+ console.log();
160
+ console.log("## 8) ralplan-state.json repository_binding");
161
+ console.log(JSON.stringify({ run_id: state.run_id, repository_binding: state.repository_binding }, null, 2));
162
+ if (!state.repository_binding) {
163
+ console.error("FAIL: ralplan state missing repository_binding");
164
+ process.exit(1);
165
+ }
166
+
167
+ // 7) Ralplan stage write on LEFT succeeds and echoes repository_binding
168
+ console.log();
169
+ console.log("## 9) gjc ralplan --write planner on LEFT (match)");
170
+ const notePath = path.join(left, "dogfood-planner.md");
171
+ await fsp.writeFile(notePath, "# dogfood planner\n");
172
+ const writeLeft = await runCli(
173
+ left,
174
+ ["ralplan", "--write", "--stage", "planner", "--stage_n", "1", "--artifact", notePath, "--json"],
175
+ env,
176
+ );
177
+ console.log(`exit=${writeLeft.code}`);
178
+ console.log(writeLeft.stdout.trim() || writeLeft.stderr.trim());
179
+ if (writeLeft.code !== 0) process.exit(1);
180
+ const writePayload = JSON.parse(writeLeft.stdout) as { repository_binding?: { worktreeRoot?: string } };
181
+ if (!writePayload.repository_binding?.worktreeRoot) {
182
+ console.error("FAIL: write receipt missing repository_binding");
183
+ process.exit(1);
184
+ }
185
+
186
+ // 8) Copy seed authority into RIGHT session layout → stage write fails closed
187
+ console.log();
188
+ console.log("## 10) ralplan --write on RIGHT with LEFT binding (fail-closed)");
189
+ const rightStateDir = path.join(right, ".gjc", `_session-${sessionId}`, "state");
190
+ await fsp.mkdir(rightStateDir, { recursive: true });
191
+ await fsp.copyFile(statePath, path.join(rightStateDir, "ralplan-state.json"));
192
+ const writeRight = await runCli(
193
+ right,
194
+ ["ralplan", "--write", "--stage", "architect", "--stage_n", "1", "--artifact", "# arch\n", "--json"],
195
+ env,
196
+ );
197
+ console.log(`exit=${writeRight.code}`);
198
+ console.log((writeRight.stderr || writeRight.stdout).trim());
199
+ if (writeRight.code === 0) {
200
+ console.error("FAIL: expected sibling write to fail closed");
201
+ process.exit(1);
202
+ }
203
+ console.log("fail_closed: ralplan write on sibling rejected");
204
+
205
+ console.log();
206
+ console.log("DOGFOOD_OK");
207
+ }
208
+
209
+ await main();
@@ -39,6 +39,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
39
39
  "agent_session:setForcedToolChoice": "internal accessor/plumbing, not a user-facing control seam",
40
40
  "agent_session:getActiveSkillState": "internal accessor/plumbing, not a user-facing control seam",
41
41
  "agent_session:getActiveSkillPhase": "internal accessor/plumbing, not a user-facing control seam",
42
+ "agent_session:getDeepInterviewAskStage":
43
+ "internal AskTool schema-selection accessor, not a user-facing SDK control seam",
42
44
  "agent_session:peekQueueInvoker": "internal accessor/plumbing, not a user-facing control seam",
43
45
  "agent_session:peekStandingResolveHandler": "internal accessor/plumbing, not a user-facing control seam",
44
46
  "agent_session:setStandingResolveHandler": "internal accessor/plumbing, not a user-facing control seam",
@@ -147,6 +149,8 @@ const LOCKED_EXCLUSIONS: Readonly<Record<string, string>> = {
147
149
  "agent_session:prepareContributionPrep": "internal accessor/plumbing, not a user-facing control seam",
148
150
  "agent_session:setResourceSampler": "internal accessor/plumbing, not a user-facing control seam",
149
151
  "agent_session:setRetainedMemorySampler": "internal accessor/plumbing, not a user-facing control seam",
152
+ "agent_session:createBtwConversationScope":
153
+ "internal privacy-scoped side-chat snapshot factory, not a user-facing SDK control seam",
150
154
  "agent_session:recordBashResult": "internal accessor/plumbing, not a user-facing control seam",
151
155
  "agent_session:executePython": "internal accessor/plumbing, not a user-facing control seam",
152
156
  "agent_session:assertEvalExecutionAllowed": "internal accessor/plumbing, not a user-facing control seam",
@@ -60,11 +60,11 @@ function validateRowReceipts(rows: ManifestAdapterRow[]): void {
60
60
  }
61
61
  counts.set(row.adapter, (counts.get(row.adapter) ?? 0) + 1);
62
62
  }
63
- if (rows.length !== ADAPTERS.length * 91)
64
- throw new Error(`Expected ${ADAPTERS.length * 91} adapter rows; received ${rows.length}.`);
63
+ if (rows.length !== ADAPTERS.length * 93)
64
+ throw new Error(`Expected ${ADAPTERS.length * 93} adapter rows; received ${rows.length}.`);
65
65
  for (const adapter of ADAPTERS) {
66
- if (counts.get(adapter) !== 91)
67
- throw new Error(`Expected 91 ${adapter} rows; received ${counts.get(adapter) ?? 0}.`);
66
+ if (counts.get(adapter) !== 93)
67
+ throw new Error(`Expected 93 ${adapter} rows; received ${counts.get(adapter) ?? 0}.`);
68
68
  }
69
69
  process.stdout.write(
70
70
  `manifest check: ${rows.length} row receipts (${[...counts].map(([adapter, count]) => `${adapter}=${count}`).join(", ")})\n`,
@@ -41,6 +41,8 @@ const disabledProviders = new Set<string>();
41
41
 
42
42
  /** Settings manager for persistence (if set) */
43
43
  let settings: Settings | null = null;
44
+ /** Session-local settings keyed by normalized working directory. */
45
+ const settingsByCwd = new Map<string, Settings>();
44
46
 
45
47
  // =============================================================================
46
48
  // Registration API
@@ -208,8 +210,9 @@ async function loadImpl<T>(
208
210
  * Filter providers based on options and disabled state.
209
211
  */
210
212
  function filterProviders<T>(capability: Capability<T>, options: LoadOptions): Provider<T>[] {
211
- let providers = (capability.providers as Provider<T>[]).filter(p => !disabledProviders.has(p.id));
212
-
213
+ const activeSettings = options.settings ?? settingsByCwd.get(path.normalize(options.cwd ?? getProjectDir()));
214
+ const activeDisabledProviders = new Set(activeSettings?.get("disabledProviders") ?? disabledProviders);
215
+ let providers = (capability.providers as Provider<T>[]).filter(p => !activeDisabledProviders.has(p.id));
213
216
  if (options.providers) {
214
217
  const allowed = new Set(options.providers);
215
218
  providers = providers.filter(p => allowed.has(p.id));
@@ -249,63 +252,68 @@ export async function loadCapability<T>(capabilityId: string, options: LoadOptio
249
252
  * Call this once on startup to enable persistent provider state.
250
253
  */
251
254
  export function initializeWithSettings(activeSettings: Settings): void {
255
+ settingsByCwd.set(path.normalize(activeSettings.getCwd()), activeSettings);
252
256
  settings = activeSettings;
253
- // Load disabled providers from settings
254
- const disabled = settings.get("disabledProviders");
257
+ const disabled = activeSettings.get("disabledProviders");
255
258
  disabledProviders.clear();
256
- for (const id of disabled) {
257
- disabledProviders.add(id);
258
- }
259
+ for (const id of disabled) disabledProviders.add(id);
259
260
  }
260
261
 
261
262
  /**
262
263
  * Persist current disabled providers to settings.
263
264
  */
264
- function persistDisabledProviders(): void {
265
- if (settings) {
266
- settings.set("disabledProviders", Array.from(disabledProviders));
267
- }
265
+ function persistDisabledProviders(activeSettings: Settings, providers: ReadonlySet<string>): void {
266
+ activeSettings.set("disabledProviders", Array.from(providers));
268
267
  }
269
268
 
270
- /**
271
- * Disable a provider globally (across all capabilities).
272
- */
273
- export function disableProvider(providerId: string): void {
274
- disabledProviders.add(providerId);
275
- persistDisabledProviders();
269
+ /** Disable a provider for the supplied session settings. */
270
+ export function disableProvider(
271
+ providerId: string,
272
+ activeSettings: Settings = settings ??
273
+ (() => {
274
+ throw new Error("Capability settings unavailable");
275
+ })(),
276
+ ): void {
277
+ const providers = new Set(activeSettings.get("disabledProviders"));
278
+ providers.add(providerId);
279
+ persistDisabledProviders(activeSettings, providers);
276
280
  }
277
281
 
278
- /**
279
- * Enable a previously disabled provider.
280
- */
281
- export function enableProvider(providerId: string): void {
282
- disabledProviders.delete(providerId);
283
- persistDisabledProviders();
282
+ /** Enable a provider for the supplied session settings. */
283
+ export function enableProvider(
284
+ providerId: string,
285
+ activeSettings: Settings = settings ??
286
+ (() => {
287
+ throw new Error("Capability settings unavailable");
288
+ })(),
289
+ ): void {
290
+ const providers = new Set(activeSettings.get("disabledProviders"));
291
+ providers.delete(providerId);
292
+ persistDisabledProviders(activeSettings, providers);
284
293
  }
285
294
 
286
- /**
287
- * Check if a provider is enabled.
288
- */
289
- export function isProviderEnabled(providerId: string): boolean {
290
- return !disabledProviders.has(providerId);
295
+ /** Check whether a provider is enabled by the supplied session settings. */
296
+ export function isProviderEnabled(
297
+ providerId: string,
298
+ activeSettings: Settings | undefined = settings ?? undefined,
299
+ ): boolean {
300
+ return !new Set(activeSettings?.get("disabledProviders") ?? disabledProviders).has(providerId);
291
301
  }
292
302
 
293
- /**
294
- * Get list of all disabled provider IDs.
295
- */
296
- export function getDisabledProviders(): string[] {
297
- return Array.from(disabledProviders);
303
+ /** Get disabled providers from the supplied session settings. */
304
+ export function getDisabledProviders(activeSettings: Settings | undefined = settings ?? undefined): string[] {
305
+ return [...(activeSettings?.get("disabledProviders") ?? disabledProviders)];
298
306
  }
299
307
 
300
- /**
301
- * Set disabled providers from a list (replaces current set).
302
- */
303
- export function setDisabledProviders(providerIds: string[]): void {
304
- disabledProviders.clear();
305
- for (const id of providerIds) {
306
- disabledProviders.add(id);
307
- }
308
- persistDisabledProviders();
308
+ /** Replace disabled providers for the supplied session settings. */
309
+ export function setDisabledProviders(
310
+ providerIds: string[],
311
+ activeSettings: Settings = settings ??
312
+ (() => {
313
+ throw new Error("Capability settings unavailable");
314
+ })(),
315
+ ): void {
316
+ persistDisabledProviders(activeSettings, new Set(providerIds));
309
317
  }
310
318
 
311
319
  // =============================================================================
@@ -6,6 +6,7 @@
6
6
  * and get back a unified array of MCP servers.
7
7
  */
8
8
 
9
+ import type { Settings } from "../config/settings";
9
10
  /**
10
11
  * Context passed to every provider loader.
11
12
  */
@@ -72,6 +73,8 @@ export interface LoadOptions {
72
73
  includeDisabled?: boolean;
73
74
  /** Explicit disabled extension IDs to apply instead of settings. */
74
75
  disabledExtensions?: string[];
76
+ /** Session settings whose provider policy applies to this load. */
77
+ settings?: Settings;
75
78
  }
76
79
 
77
80
  /**
package/src/cli/args.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  /**
2
- * CLI argument parsing and help display
2
+ * CLI argument parsing
3
3
  */
4
4
  import * as path from "node:path";
5
5
  import { type Effort, THINKING_EFFORTS } from "@gajae-code/ai";
6
- import { APP_NAME, CONFIG_DIR_NAME, logger } from "@gajae-code/utils";
6
+ import { logger } from "@gajae-code/utils";
7
7
  import { CliParseError } from "@gajae-code/utils/cli";
8
- import chalk from "chalk";
9
8
  import { parseEffort } from "../thinking";
10
9
  import { BUILTIN_TOOLS } from "../tools";
11
10
 
@@ -253,92 +252,3 @@ export function parseArgs(args: string[]): Args {
253
252
 
254
253
  return result;
255
254
  }
256
-
257
- export function getExtraHelpText(): string {
258
- return `${chalk.bold("Environment Variables:")}
259
- ${chalk.dim("# Core Providers")}
260
- ANTHROPIC_API_KEY - Anthropic Claude models
261
- ANTHROPIC_OAUTH_TOKEN - Anthropic OAuth (takes precedence over API key)
262
- CLAUDE_CODE_USE_FOUNDRY - Enable Anthropic Foundry mode (uses Foundry endpoint + mTLS)
263
- FOUNDRY_BASE_URL - Anthropic Foundry base URL (e.g., https://<foundry-host>)
264
- ANTHROPIC_FOUNDRY_API_KEY - Anthropic token used as Authorization: Bearer <token> in Foundry mode
265
- ANTHROPIC_CUSTOM_HEADERS - Extra Foundry headers (e.g., "user-id: USERNAME")
266
- CLAUDE_CODE_CLIENT_CERT - Client certificate (PEM path or inline PEM) for mTLS
267
- CLAUDE_CODE_CLIENT_KEY - Client private key (PEM path or inline PEM) for mTLS
268
- NODE_EXTRA_CA_CERTS - CA bundle path (or inline PEM) for server certificate validation
269
- OPENAI_API_KEY - OpenAI GPT models
270
- GEMINI_API_KEY - Google Gemini models
271
- GITHUB_TOKEN - GitHub Copilot (or GH_TOKEN, COPILOT_GITHUB_TOKEN)
272
-
273
- ${chalk.dim("# Additional LLM Providers")}
274
- AZURE_OPENAI_API_KEY - Azure OpenAI models
275
- GROQ_API_KEY - Groq models
276
- CEREBRAS_API_KEY - Cerebras models
277
- XAI_API_KEY - xAI Grok models
278
- OPENROUTER_API_KEY - OpenRouter aggregated models
279
- KILO_API_KEY - Kilo Gateway models
280
- MISTRAL_API_KEY - Mistral models
281
- ZAI_API_KEY - z.ai models (ZhipuAI/GLM)
282
- MINIMAX_API_KEY - MiniMax models
283
- OPENCODE_API_KEY - OpenCode Zen/OpenCode Go models
284
- CURSOR_ACCESS_TOKEN - Cursor AI models
285
- AI_GATEWAY_API_KEY - Vercel AI Gateway
286
-
287
- ${chalk.dim("# Cloud Providers")}
288
- AWS_PROFILE - AWS Bedrock (or AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY)
289
- GOOGLE_CLOUD_PROJECT - Google Vertex AI (requires GOOGLE_CLOUD_LOCATION)
290
- GOOGLE_APPLICATION_CREDENTIALS - Service account for Vertex AI
291
-
292
- ${chalk.dim("# Search & Tools")}
293
- EXA_API_KEY - Exa web search
294
- BRAVE_API_KEY - Brave web search
295
- PERPLEXITY_API_KEY - Perplexity web search (API)
296
- PERPLEXITY_COOKIES - Perplexity web search (session cookie)
297
- TAVILY_API_KEY - Tavily web search
298
- ANTHROPIC_SEARCH_API_KEY - Anthropic search provider
299
-
300
- ${chalk.dim("# Configuration")}
301
- GJC_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
302
- GJC_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
303
- GJC_SMOL_MODEL - Override smol/fast model (see --smol)
304
- GJC_SLOW_MODEL - Override slow/reasoning model (see --slow)
305
- GJC_PLAN_MODEL - Override planning model (see --plan)
306
- GJC_NO_PTY - Disable PTY-based interactive bash execution
307
- --tmux - Launch interactive startup inside a fresh tmux session
308
- gjc session - List, inspect, create, remove, or attach tagged GJC-managed tmux sessions
309
- GJC_LAUNCH_POLICY - Launch policy for --tmux startup: tmux or direct
310
- GJC_TMUX_SESSION - Explicit tmux session name override for --tmux startup
311
- GJC_TMUX_PROFILE - Apply GJC tmux scroll/mouse/clipboard profile to --tmux sessions (set 0/off to skip)
312
- GJC_MOUSE - Mouse-wheel scroll in --tmux sessions (set 0/off to let the host terminal scroll)
313
-
314
- For complete environment variable reference, see:
315
- ${chalk.dim("docs/environment-variables.md")}
316
- ${chalk.bold("Available Tools (default-enabled unless noted):")}
317
- read - Read file contents
318
- bash - Execute bash commands
319
- edit - Edit files with find/replace
320
- write - Write files (creates/overwrites)
321
- grep - Search file contents
322
- find - Find files by glob pattern
323
- lsp - Language server protocol (code intelligence)
324
- python - Execute Python code (requires: ${APP_NAME} setup python)
325
- notebook - Edit Jupyter notebooks
326
- browser - Browser automation (Puppeteer)
327
- task - Launch sub-agents for parallel tasks
328
- todo_write - Manage todo/task lists
329
- web_search - Search the web
330
- ask - Ask user questions (interactive mode only)
331
-
332
- ${chalk.bold("Useful Commands:")}
333
- ${APP_NAME} --list-models - List configured provider models
334
- ${APP_NAME} --help - Show this help`;
335
- }
336
-
337
- export function printHelp(): void {
338
- process.stdout.write(
339
- `${chalk.bold(APP_NAME)} - red-claw AI coding assistant\n\n` +
340
- `Run ${APP_NAME} --help for full command and option details.\n` +
341
- `Run ${APP_NAME} <command> --help for command-specific help.\n\n` +
342
- `${getExtraHelpText()}\n`,
343
- );
344
- }
@@ -7,7 +7,6 @@ import { createInterface } from "node:readline/promises";
7
7
  import { APP_NAME } from "@gajae-code/utils/dirs";
8
8
  import chalk from "chalk";
9
9
  import { Settings, type SettingsAtomicPatch } from "../config/settings";
10
- import { isProcessIncarnation, processIncarnation } from "../sdk/broker/process-incarnation";
11
10
  import { type EnsureChatDaemonResult, ensureDiscordDaemon, ensureSlackDaemon } from "../sdk/bus/chat-daemon-control";
12
11
  import { getNotificationConfig, maskToken, tokenFingerprint } from "../sdk/bus/config";
13
12
  import {
@@ -33,7 +32,7 @@ import {
33
32
  import {
34
33
  type EnsureTelegramDaemonDetailedResult,
35
34
  ensureTelegramDaemonRunningDetailed,
36
- readDaemonState,
35
+ resolveTelegramSetupPreflight,
37
36
  } from "../sdk/bus/telegram-daemon";
38
37
  import { runDaemonInternal } from "../sdk/bus/telegram-daemon-cli";
39
38
  import {
@@ -458,42 +457,10 @@ function proposedIdentityFromSetupPreflight(
458
457
 
459
458
  async function resolveSetupPreflight(settings: Settings, deps: NotifyCommandDeps): Promise<TelegramSetupPreflight> {
460
459
  if (deps.setupPreflight) return deps.setupPreflight;
461
- const cfg = getNotificationConfig(settings);
462
- try {
463
- const state = await readDaemonState(settings);
464
- if (!state) return { storedChatId: cfg.chatId };
465
- const validPid = Number.isSafeInteger(state.pid) && state.pid > 0;
466
- if (!validPid || !(deps.setupPidAlive ?? daemonPidAlive)(state.pid)) return { storedChatId: cfg.chatId };
467
- const persistedIncarnation = state.incarnation;
468
- const currentIncarnation = (deps.setupPidIncarnation ?? processIncarnation)(state.pid);
469
- if (
470
- !isProcessIncarnation(persistedIncarnation) ||
471
- !isProcessIncarnation(currentIncarnation) ||
472
- persistedIncarnation !== currentIncarnation
473
- )
474
- return { storedChatId: cfg.chatId };
475
- return {
476
- storedChatId: cfg.chatId,
477
- daemon: {
478
- live: true,
479
- tokenFingerprint: typeof state.tokenFingerprint === "string" ? state.tokenFingerprint : undefined,
480
- chatId: typeof state.chatId === "string" ? state.chatId : undefined,
481
- },
482
- };
483
- } catch {
484
- // A state read failure is not proof of a live daemon; proceed normally. The
485
- // daemon's own 409 handling remains the backstop against poller contention.
486
- return { storedChatId: cfg.chatId };
487
- }
488
- }
489
-
490
- function daemonPidAlive(pid: number): boolean {
491
- try {
492
- process.kill(pid, 0);
493
- return true;
494
- } catch (error) {
495
- return (error as NodeJS.ErrnoException).code === "EPERM";
496
- }
460
+ return await resolveTelegramSetupPreflight(settings, {
461
+ pidAlive: deps.setupPidAlive,
462
+ pidIncarnation: deps.setupPidIncarnation,
463
+ });
497
464
  }
498
465
 
499
466
  type TokenPromptInput = NodeJS.ReadStream & {
@@ -18,7 +18,11 @@ import {
18
18
  } from "../hooks/codex-native-hooks-config";
19
19
  import { theme } from "../modes/theme/theme";
20
20
  import { formatCredentialAutoImportResult, runExternalCredentialAutoImport } from "../setup/credential-auto-import";
21
- import { filterAutoImportOAuthCredentials, formatDiscoverySummary } from "../setup/credential-import";
21
+ import {
22
+ formatDiscoverySummary,
23
+ getAutoImportOAuthCredentialSkips,
24
+ isAutoImportOAuthCredential,
25
+ } from "../setup/credential-import";
22
26
  import {
23
27
  formatHermesSetupResult,
24
28
  type HermesSetupFlags,
@@ -596,8 +600,10 @@ export async function handleCredentialsSetup(
596
600
  trigger: "setup-cli",
597
601
  });
598
602
  const result = preview.discovery ?? { importable: [], skipped: [], environment: [] };
599
- const candidates = filterAutoImportOAuthCredentials(result.importable);
600
- const filteredResult = { ...result, importable: candidates };
603
+ const now = Date.now();
604
+ const candidates = result.importable.filter(credential => isAutoImportOAuthCredential(credential, now));
605
+ const autoImportSkips = getAutoImportOAuthCredentialSkips(result.importable, now);
606
+ const filteredResult = { ...result, importable: candidates, skipped: [...result.skipped, ...autoImportSkips] };
601
607
  const redactedPlan = {
602
608
  importable: candidates.map(c => ({
603
609
  provider: c.provider,
@@ -607,7 +613,7 @@ export async function handleCredentialsSetup(
607
613
  expiresAt: c.expiresAt,
608
614
  redactedToken: c.redactedToken,
609
615
  })),
610
- skipped: result.skipped,
616
+ skipped: filteredResult.skipped,
611
617
  environment: result.environment,
612
618
  keychainChecked: flags.keychain === true,
613
619
  };