@gajae-code/coding-agent 0.12.5 → 0.12.7

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 (91) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +4 -1
  3. package/dist/types/async/job-manager.d.ts +36 -1
  4. package/dist/types/extensibility/extensions/runner.d.ts +9 -7
  5. package/dist/types/gjc-runtime/launch-tmux.d.ts +22 -3
  6. package/dist/types/gjc-runtime/psmux-detect.d.ts +3 -0
  7. package/dist/types/gjc-runtime/team-launch.d.ts +3 -1
  8. package/dist/types/gjc-runtime/team-runtime.d.ts +14 -2
  9. package/dist/types/gjc-runtime/team-store.d.ts +0 -4
  10. package/dist/types/gjc-runtime/team-workers.d.ts +5 -0
  11. package/dist/types/gjc-runtime/tmux-common.d.ts +1 -0
  12. package/dist/types/gjc-runtime/tmux-provider-context.d.ts +57 -0
  13. package/dist/types/gjc-runtime/tmux-sessions.d.ts +8 -1
  14. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +3 -0
  15. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +21 -4
  16. package/dist/types/modes/components/gjc-bundle-settings.d.ts +1 -0
  17. package/dist/types/modes/components/plugin-settings.d.ts +2 -0
  18. package/dist/types/modes/components/settings-selector.d.ts +2 -0
  19. package/dist/types/sdk/broker/broker.d.ts +18 -0
  20. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +13 -0
  21. package/dist/types/sdk/broker/lifecycle.d.ts +1 -0
  22. package/dist/types/sdk/bus/chat-daemon-control.d.ts +2 -3
  23. package/dist/types/sdk/bus/index.d.ts +4 -0
  24. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +2 -2
  25. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -3
  26. package/dist/types/sdk/bus/topic-registry.d.ts +63 -3
  27. package/dist/types/session/agent-session.d.ts +5 -4
  28. package/dist/types/session/attempt-record-store.d.ts +19 -0
  29. package/dist/types/session/session-manager.d.ts +1 -0
  30. package/dist/types/session/session-storage.d.ts +8 -0
  31. package/dist/types/task/receipt.d.ts +1 -0
  32. package/dist/types/task/types.d.ts +43 -0
  33. package/dist/types/tools/subagent.d.ts +15 -0
  34. package/dist/types/tools/todo-write.d.ts +3 -0
  35. package/package.json +8 -8
  36. package/scripts/capture-sticky-viewport-showcase.ts +131 -5
  37. package/scripts/verify-gjc-sdk-canonicalization.ts +128 -183
  38. package/scripts/verify-sticky-viewport-showcase.ts +620 -93
  39. package/src/async/job-manager.ts +430 -46
  40. package/src/config/model-profiles.ts +8 -0
  41. package/src/config/settings-schema.ts +17 -3
  42. package/src/defaults/gjc/skills/ultragoal/SKILL.md +2 -0
  43. package/src/extensibility/extensions/runner.ts +80 -6
  44. package/src/extensibility/extensions/wrapper.ts +22 -15
  45. package/src/extensibility/runtime-skill-discovery.ts +4 -6
  46. package/src/gjc-runtime/launch-tmux.ts +309 -293
  47. package/src/gjc-runtime/managed-owner-supervisor.ts +6 -2
  48. package/src/gjc-runtime/psmux-detect.ts +52 -6
  49. package/src/gjc-runtime/team-launch.ts +50 -2
  50. package/src/gjc-runtime/team-runtime.ts +716 -251
  51. package/src/gjc-runtime/team-store.ts +25 -7
  52. package/src/gjc-runtime/team-workers.ts +161 -1
  53. package/src/gjc-runtime/tmux-common.ts +16 -4
  54. package/src/gjc-runtime/tmux-gc.ts +37 -5
  55. package/src/gjc-runtime/tmux-owner-isolation.ts +2 -0
  56. package/src/gjc-runtime/tmux-provider-context.ts +451 -0
  57. package/src/gjc-runtime/tmux-sessions.ts +409 -156
  58. package/src/gjc-runtime/ultragoal-change-set.ts +90 -44
  59. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +7 -0
  60. package/src/gjc-runtime/ultragoal-runtime.ts +360 -156
  61. package/src/internal-urls/docs-index.generated.ts +4 -4
  62. package/src/internal-urls/local-protocol.ts +39 -18
  63. package/src/modes/acp/acp-agent.ts +27 -13
  64. package/src/modes/components/gjc-bundle-settings.ts +2 -0
  65. package/src/modes/components/plugin-settings.ts +5 -0
  66. package/src/modes/components/settings-selector.ts +4 -0
  67. package/src/modes/controllers/selector-controller.ts +1 -0
  68. package/src/prompts/tools/monitor.md +4 -4
  69. package/src/sdk/broker/broker.ts +93 -10
  70. package/src/sdk/broker/lifecycle-ledger.ts +227 -11
  71. package/src/sdk/broker/lifecycle.ts +691 -92
  72. package/src/sdk/bus/chat-daemon-control.ts +30 -6
  73. package/src/sdk/bus/index.ts +23 -6
  74. package/src/sdk/bus/telegram-daemon-contract.ts +2 -2
  75. package/src/sdk/bus/telegram-daemon.ts +283 -44
  76. package/src/sdk/bus/topic-registry.ts +133 -10
  77. package/src/sdk/session.ts +7 -6
  78. package/src/session/agent-session.ts +752 -339
  79. package/src/session/attempt-record-store.ts +106 -0
  80. package/src/session/internal/managed-session-scope.ts +252 -67
  81. package/src/session/internal/managed-session-storage.ts +1 -0
  82. package/src/session/session-manager.ts +7 -0
  83. package/src/session/session-storage.ts +146 -24
  84. package/src/task/index.ts +230 -19
  85. package/src/task/provider-retry-status.ts +4 -1
  86. package/src/task/receipt.ts +2 -0
  87. package/src/task/types.ts +10 -0
  88. package/src/tools/monitor.ts +7 -1
  89. package/src/tools/output-meta.ts +10 -2
  90. package/src/tools/subagent.ts +97 -75
  91. package/src/tools/todo-write.ts +47 -0
@@ -92,6 +92,10 @@ function lifecycleRoot(): {
92
92
  function commandDigest(command: readonly string[]): string {
93
93
  return crypto.createHash("sha256").update(JSON.stringify(command)).digest("hex");
94
94
  }
95
+ async function managedOwnerProcessProvenance(pid: number): Promise<string | null> {
96
+ if (process.platform === "linux") return await readLinuxProcStartTime(pid);
97
+ return Process.fromPid(pid)?.incarnation ?? null;
98
+ }
95
99
 
96
100
  async function writeDurableExclusive(file: string, value: object): Promise<void> {
97
101
  const handle = await fs.open(file, "wx", 0o600);
@@ -132,7 +136,7 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
132
136
  };
133
137
  process.removeAllListeners("SIGTERM");
134
138
  process.on("SIGTERM", captureEarlySigterm);
135
- const supervisorStartTime = await readLinuxProcStartTime(process.pid);
139
+ const supervisorStartTime = await managedOwnerProcessProvenance(process.pid);
136
140
  if (!supervisorStartTime) throw new Error("managed_owner_supervisor_start_time_unavailable");
137
141
  await fs.mkdir(root, { recursive: true, mode: 0o700 });
138
142
  const childToken = crypto.randomUUID();
@@ -157,7 +161,7 @@ export async function runManagedOwnerSupervisor(): Promise<void> {
157
161
  stderr: "inherit",
158
162
  env: { ...process.env, [MANAGED_OWNER_CHILD_TOKEN_ENV]: childToken },
159
163
  });
160
- const childStartTime = await readLinuxProcStartTime(child.pid);
164
+ const childStartTime = await managedOwnerProcessProvenance(child.pid);
161
165
  if (!childStartTime) throw new Error("managed_owner_child_start_time_unavailable");
162
166
  const childProcess = Process.fromPid(child.pid);
163
167
  if (!childProcess) throw new Error("managed_owner_child_reference_unavailable");
@@ -1,3 +1,4 @@
1
+ import { createHash } from "node:crypto";
1
2
  import * as fs from "node:fs";
2
3
 
3
4
  /**
@@ -62,8 +63,32 @@ export type ExecutableIdentityResolver = (path: string) => string | null;
62
63
  const DEFAULT_EXECUTABLE_IDENTITY_RESOLVER: ExecutableIdentityResolver = executablePath => {
63
64
  try {
64
65
  const realPath = fs.realpathSync.native(executablePath);
65
- const stat = fs.statSync(realPath);
66
- return stat.ino === 0 ? realPath.toLowerCase() : `${stat.dev}:${stat.ino}`;
66
+ const fd = fs.openSync(realPath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
67
+ try {
68
+ const before = fs.fstatSync(fd, { bigint: true });
69
+ if (!before.isFile() || before.dev === 0n || before.ino === 0n) return null;
70
+ const bytes = fs.readFileSync(fd);
71
+ const after = fs.fstatSync(fd, { bigint: true });
72
+ const named = fs.lstatSync(realPath, { bigint: true });
73
+ if (
74
+ !named.isFile() ||
75
+ named.isSymbolicLink() ||
76
+ after.dev !== before.dev ||
77
+ after.ino !== before.ino ||
78
+ after.size !== before.size ||
79
+ after.mtimeNs !== before.mtimeNs ||
80
+ after.ctimeNs !== before.ctimeNs ||
81
+ named.dev !== before.dev ||
82
+ named.ino !== before.ino ||
83
+ named.size !== before.size ||
84
+ named.mtimeNs !== before.mtimeNs ||
85
+ named.ctimeNs !== before.ctimeNs
86
+ )
87
+ return null;
88
+ return `${before.dev}:${before.ino}:${before.size}:${before.mtimeNs}:${before.ctimeNs}:${createHash("sha256").update(bytes).digest("hex")}`;
89
+ } finally {
90
+ fs.closeSync(fd);
91
+ }
67
92
  } catch {
68
93
  return null;
69
94
  }
@@ -152,6 +177,14 @@ function isNamedPsmuxCommand(command: string): boolean {
152
177
  function resolveBinaryPath(candidate: string): string | null {
153
178
  return activeBinaryResolver(candidate);
154
179
  }
180
+ /** Resolve through the same identity-aware seams used by Windows alias detection. */
181
+ export function resolveGjcTmuxExecutablePath(command: string): string | null {
182
+ return resolveBinaryPath(command);
183
+ }
184
+
185
+ export function resolveGjcTmuxExecutableIdentity(executablePath: string): string | null {
186
+ return activeExecutableIdentityResolver(executablePath);
187
+ }
155
188
 
156
189
  function detectPsmuxForCommand(command: string, runner: PsmuxSpawnRunner): boolean {
157
190
  const resolved = resolveBinaryPath(command);
@@ -291,11 +324,24 @@ export function resolveGjcTmuxBinary(options: ResolveGjcTmuxBinaryOptions = {}):
291
324
  return { command: explicit, isPsmux, viaExplicitOverride: true };
292
325
  }
293
326
  if (platform === "win32") {
327
+ const candidates: Array<{ command: string; identity: string }> = [];
294
328
  for (const candidate of PSMUX_BINARY_NAMES) {
295
- if (resolveBinaryPath(candidate)) {
296
- const isPsmux = classifyWindowsTmuxAlias(candidate, env, runner);
297
- return { command: candidate, isPsmux, viaExplicitOverride: false };
298
- }
329
+ const executablePath = resolveBinaryPath(candidate);
330
+ if (!executablePath) continue;
331
+ const isPsmuxAlias = candidate !== "tmux" || detectPsmuxForCommand(candidate, runner);
332
+ if (!isPsmuxAlias) continue;
333
+ const identity = activeExecutableIdentityResolver(executablePath);
334
+ if (!identity)
335
+ throw new Error(`gjc_tmux_provider_ambiguous: Windows ${candidate} executable identity is unavailable`);
336
+ candidates.push({ command: candidate, identity });
337
+ }
338
+ if (candidates.length > 0) {
339
+ const identities = new Set(candidates.map(candidate => candidate.identity));
340
+ if (identities.size !== 1)
341
+ throw new Error("gjc_tmux_provider_ambiguous: Windows psmux aliases resolve to different executables");
342
+ // The order is the canonical provider spelling, then its compatibility
343
+ // aliases. Identity equality above makes PATH-order alias selection safe.
344
+ return { command: candidates[0]!.command, isPsmux: true, viaExplicitOverride: false };
299
345
  }
300
346
  }
301
347
  const tmuxPath = resolveBinaryPath("tmux");
@@ -2,6 +2,11 @@ import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import { SPAWN_PROVENANCE_ENV } from "../sdk/bus/config";
4
4
  import { resolveSessionIdFromSources } from "./session-resolution";
5
+ import {
6
+ GJC_COORDINATOR_SESSION_ID_ENV,
7
+ GJC_TMUX_OWNER_GENERATION_ENV,
8
+ GJC_TMUX_OWNER_STATE_DIR_ENV,
9
+ } from "./session-state-sidecar";
5
10
  import type {
6
11
  GjcTeamConfig,
7
12
  GjcTeamSnapshot,
@@ -13,6 +18,12 @@ import type {
13
18
  GjcTeamWorktreeMode,
14
19
  } from "./team-runtime";
15
20
  import { createInitialGjcTeamWorkerMemoryGuardLedger, workerMemoryGuardLedgerPath } from "./team-worker-memory-guard";
21
+ import {
22
+ bindGjcTmuxProviderAuthority,
23
+ type ProviderAuthority,
24
+ readGjcTmuxProviderAuthoritySync,
25
+ resolveGjcTmuxProviderContext,
26
+ } from "./tmux-provider-context";
16
27
 
17
28
  /** Launch-specific option wiring kept separate from runtime dispatch. */
18
29
  export function withTeamLaunchTransport(
@@ -97,6 +108,7 @@ export function buildWorkerCommand(
97
108
  interface GjcTmuxBinary {
98
109
  command: string;
99
110
  isPsmux: boolean;
111
+ viaExplicitOverride: boolean;
100
112
  }
101
113
 
102
114
  interface GjcTmuxLeaderContext {
@@ -115,7 +127,11 @@ export interface GjcTeamLaunchRuntime {
115
127
  teamDir(stateRoot: string, teamName: string): string;
116
128
  resolveDefaultWorktreeMode(mode?: GjcTeamWorktreeMode): GjcTeamWorktreeMode;
117
129
  resolveTmuxBinary(input: { env: NodeJS.ProcessEnv; platform: NodeJS.Platform }): GjcTmuxBinary;
118
- readTmuxLeaderContext(tmuxCommand: string, env: NodeJS.ProcessEnv): GjcTmuxLeaderContext;
130
+ readTmuxLeaderContext(
131
+ tmuxCommand: string,
132
+ env: NodeJS.ProcessEnv,
133
+ authority: ProviderAuthority,
134
+ ): GjcTmuxLeaderContext;
119
135
  buildWorkers(workerCount: number, agentType: string, stateRoot: string): GjcTeamWorker[];
120
136
  buildInitialTasks(task: string, workers: GjcTeamWorker[]): GjcTeamTask[];
121
137
  ensureWorkerWorktree(
@@ -217,9 +233,32 @@ export async function startGjcTeamLaunch(
217
233
  const platform = options.platform ?? process.platform;
218
234
  const tmuxBinary = runtime.resolveTmuxBinary({ env, platform });
219
235
  const tmuxCommand = tmuxBinary.command;
236
+ const tmuxProviderGeneration =
237
+ tmuxBinary.isPsmux && platform === "win32" ? env[GJC_TMUX_OWNER_GENERATION_ENV]?.trim() : undefined;
238
+ const tmuxProvider = resolveGjcTmuxProviderContext({ binary: tmuxBinary, env, platform });
239
+ if (tmuxProvider.binary.command !== tmuxCommand) throw new Error("gjc_team_tmux_provider_command_mismatch");
240
+ const launchSessionId =
241
+ env[GJC_COORDINATOR_SESSION_ID_ENV]?.trim() || env.GJC_SESSION_ID?.trim() || gjcSessionId?.trim();
242
+ const launchStateDir = env[GJC_TMUX_OWNER_STATE_DIR_ENV]?.trim();
243
+ const tmuxAuthority =
244
+ tmuxBinary.isPsmux && platform === "win32" && !options.dryRun
245
+ ? launchSessionId && launchStateDir && tmuxProviderGeneration
246
+ ? readGjcTmuxProviderAuthoritySync({
247
+ stateDir: launchStateDir,
248
+ sessionId: launchSessionId,
249
+ generation: tmuxProviderGeneration,
250
+ })
251
+ : (() => {
252
+ throw new Error("gjc_team_tmux_provider_authority_unavailable");
253
+ })()
254
+ : bindGjcTmuxProviderAuthority(tmuxProvider, {
255
+ stateDir: stateRoot,
256
+ sessionId: teamName,
257
+ generation: "native-tmux",
258
+ });
220
259
  const tmuxContext = options.dryRun
221
260
  ? { sessionName: "dry-run", windowIndex: "0", leaderPaneId: "%dry-run-leader", target: "dry-run:0" }
222
- : runtime.readTmuxLeaderContext(tmuxCommand, env);
261
+ : runtime.readTmuxLeaderContext(tmuxCommand, env, tmuxAuthority);
223
262
  const initialWorkers = runtime.buildWorkers(options.workerCount, options.agentType, stateRoot);
224
263
  const initialTasks = runtime.buildInitialTasks(options.task, initialWorkers);
225
264
  const workers: GjcTeamWorker[] = [];
@@ -267,9 +306,17 @@ export async function startGjcTeamLaunch(
267
306
  ...(gjcSessionId ? { gjc_session_id: gjcSessionId } : {}),
268
307
  worker_cli_plan: workerCliPlan,
269
308
  tmux_command: tmuxCommand,
309
+ platform,
270
310
  tmux_session: tmuxContext.sessionName,
271
311
  tmux_session_name: tmuxContext.sessionName,
272
312
  tmux_target: tmuxContext.target,
313
+ ...(tmuxProviderGeneration
314
+ ? {
315
+ tmux_provider_generation: tmuxProviderGeneration,
316
+ tmux_provider_state_dir: launchStateDir!,
317
+ tmux_provider_session_id: launchSessionId!,
318
+ }
319
+ : {}),
273
320
  workspace_mode: worktreeMode.enabled ? "worktree" : "direct",
274
321
  dry_run: options.dryRun ?? false,
275
322
  leader: {
@@ -296,6 +343,7 @@ export async function startGjcTeamLaunch(
296
343
  worker_command: config.worker_command,
297
344
  worker_cli_plan: config.worker_cli_plan,
298
345
  tmux_command: config.tmux_command,
346
+ tmux_provider_generation: config.tmux_provider_generation,
299
347
  leader: config.leader,
300
348
  workers: config.workers,
301
349
  workspace_mode: config.workspace_mode,