@gajae-code/coding-agent 0.8.2 → 0.9.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.
- package/CHANGELOG.md +29 -0
- package/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/capability/rule.d.ts +8 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/update-cli.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +105 -1
- package/dist/types/discovery/index.d.ts +1 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/export/ttsr.d.ts +14 -3
- package/dist/types/gjc-runtime/deep-interview-state.d.ts +5 -2
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +7 -0
- package/dist/types/gjc-runtime/state-writer.d.ts +3 -0
- package/dist/types/gjc-runtime/team-runtime.d.ts +26 -0
- package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +119 -13
- package/dist/types/goals/runtime.d.ts +1 -0
- package/dist/types/hooks/native-skill-hook.d.ts +12 -0
- package/dist/types/modes/components/assistant-message.d.ts +5 -1
- package/dist/types/modes/components/status-line.d.ts +9 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +5 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +14 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +19 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +23 -1
- package/dist/types/modes/types.d.ts +1 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config.d.ts +6 -0
- package/dist/types/notifications/telegram-daemon.d.ts +8 -0
- package/dist/types/notifications/threaded-render.d.ts +6 -0
- package/dist/types/sdk.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +43 -1
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/messages.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +17 -1
- package/dist/types/skill-state/active-state.d.ts +18 -1
- package/dist/types/system-prompt.d.ts +18 -2
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/index.d.ts +1 -0
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/utils/file-mentions.d.ts +14 -0
- package/dist/types/utils/git.d.ts +32 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/package.json +7 -7
- package/src/append-only-mode.ts +58 -0
- package/src/autoresearch/prompt.md +1 -1
- package/src/autoresearch/tools/log-experiment.ts +1 -1
- package/src/capability/rule.ts +9 -0
- package/src/cli/config-cli.ts +27 -3
- package/src/cli/fixture-report.test.ts +230 -0
- package/src/cli/fixture-report.ts +171 -0
- package/src/cli/nofile-limit.ts +48 -0
- package/src/cli/update-cli.ts +85 -9
- package/src/cli.ts +26 -0
- package/src/commands/contribution-prep.ts +1 -1
- package/src/config/settings-schema.ts +68 -1
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +12 -13
- package/src/defaults/gjc/skills/ralplan/SKILL.md +30 -93
- package/src/defaults/gjc/skills/team/SKILL.md +16 -13
- package/src/defaults/gjc/skills/ultragoal/SKILL.md +59 -4
- package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +30 -0
- package/src/defaults/gjc-defaults.ts +9 -0
- package/src/discovery/helpers.ts +10 -0
- package/src/discovery/index.ts +1 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +60 -2
- package/src/export/ttsr.ts +96 -18
- package/src/extensibility/extensions/runner.ts +181 -219
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +156 -44
- package/src/gjc-runtime/deep-interview-state.ts +31 -2
- package/src/gjc-runtime/session-state-sidecar.ts +208 -13
- package/src/gjc-runtime/state-writer.ts +15 -0
- package/src/gjc-runtime/team-runtime.ts +164 -15
- package/src/gjc-runtime/ultragoal-guard.ts +55 -66
- package/src/gjc-runtime/ultragoal-receipt-freshness.ts +315 -0
- package/src/gjc-runtime/ultragoal-runtime.ts +1593 -143
- package/src/gjc-runtime/workflow-manifest.generated.json +113 -2
- package/src/gjc-runtime/workflow-manifest.ts +74 -1
- package/src/goals/runtime.ts +10 -2
- package/src/hindsight/backend.ts +1 -3
- package/src/hooks/native-skill-hook.ts +95 -4
- package/src/internal-urls/docs-index.generated.ts +14 -5
- package/src/main.ts +43 -2
- package/src/memories/index.ts +12 -1
- package/src/modes/components/assistant-message.ts +241 -82
- package/src/modes/components/custom-editor.ts +1 -0
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/tool-execution.ts +44 -19
- package/src/modes/controllers/command-controller.ts +39 -2
- package/src/modes/controllers/event-controller.ts +66 -47
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +9 -1
- package/src/modes/rpc/rpc-mode.ts +29 -3
- package/src/modes/rpc/rpc-types.ts +9 -0
- package/src/modes/shared/agent-wire/command-validation.ts +4 -0
- package/src/modes/shared/agent-wire/event-contract.ts +22 -0
- package/src/modes/shared/agent-wire/event-envelope.ts +82 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +17 -4
- package/src/notifications/config.ts +16 -4
- package/src/notifications/index.ts +78 -5
- package/src/notifications/telegram-daemon-cli.ts +2 -2
- package/src/notifications/telegram-daemon-control.ts +2 -2
- package/src/notifications/telegram-daemon.ts +85 -8
- package/src/notifications/threaded-render.ts +17 -1
- package/src/prompts/agents/architect.md +19 -10
- package/src/prompts/agents/critic.md +16 -18
- package/src/prompts/agents/executor.md +14 -11
- package/src/prompts/agents/planner.md +18 -13
- package/src/prompts/ci-green-request.md +3 -8
- package/src/prompts/goals/goal-continuation.md +0 -5
- package/src/prompts/goals/goal-mode-active.md +0 -5
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/plan-mode-subagent.md +3 -3
- package/src/prompts/system/project-prompt.md +0 -13
- package/src/prompts/system/subagent-system-prompt.md +3 -2
- package/src/prompts/system/system-prompt.md +26 -19
- package/src/prompts/system/volatile-project-context.md +10 -0
- package/src/prompts/system/web-search.md +4 -7
- package/src/prompts/tools/apply-patch.md +6 -0
- package/src/prompts/tools/ast-edit.md +1 -0
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/bisect.md +22 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/cron.md +0 -2
- package/src/prompts/tools/eval.md +2 -2
- package/src/prompts/tools/find.md +1 -1
- package/src/prompts/tools/github.md +6 -6
- package/src/prompts/tools/goal.md +1 -3
- package/src/prompts/tools/image-gen.md +12 -5
- package/src/prompts/tools/irc.md +5 -4
- package/src/prompts/tools/job.md +2 -2
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/monitor.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/replace.md +2 -21
- package/src/prompts/tools/rewind.md +1 -1
- package/src/prompts/tools/search-tool-bm25.md +1 -11
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/skill.md +3 -2
- package/src/prompts/tools/ssh.md +2 -1
- package/src/prompts/tools/subagent.md +2 -2
- package/src/prompts/tools/task.md +4 -5
- package/src/prompts/tools/todo-write.md +1 -1
- package/src/prompts/tools/vim.md +1 -0
- package/src/prompts/tools/web-search.md +2 -0
- package/src/prompts/tools/write.md +8 -2
- package/src/sdk.ts +32 -22
- package/src/session/agent-session.ts +732 -107
- package/src/session/file-mention-pruning.ts +64 -0
- package/src/session/messages.ts +4 -0
- package/src/session/session-manager.ts +59 -2
- package/src/skill-state/active-state.ts +141 -5
- package/src/skill-state/deep-interview-mutation-guard.ts +67 -3
- package/src/slash-commands/builtin-registry.ts +65 -0
- package/src/system-prompt.ts +57 -16
- package/src/task/executor.ts +125 -20
- package/src/task/fork-context-advisory.ts +2 -7
- package/src/task/fork-context-budget.ts +9 -0
- package/src/task/index.ts +4 -3
- package/src/task/output-manager.ts +4 -2
- package/src/task/token-log.test.ts +188 -0
- package/src/task/token-log.ts +179 -0
- package/src/tools/bisect.ts +367 -0
- package/src/tools/index.ts +3 -0
- package/src/tools/output-meta.ts +116 -4
- package/src/tools/search.ts +8 -2
- package/src/tools/skill.ts +15 -0
- package/src/utils/file-mentions.ts +39 -7
- package/src/utils/git.ts +73 -0
- package/src/web/scrapers/youtube.ts +24 -12
|
@@ -304,6 +304,18 @@ function activeStateDir(cwd: string, sessionScope?: string | ActiveSessionScope)
|
|
|
304
304
|
return layoutActiveStateDir(cwd, requireSessionId(sessionScope, "activeStateDir"));
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
type ActiveStateCacheInvalidator = (cwd?: string, sessionId?: string) => void;
|
|
308
|
+
var activeStateCacheInvalidator: ActiveStateCacheInvalidator | undefined;
|
|
309
|
+
|
|
310
|
+
export function setActiveStateCacheInvalidator(invalidator: ActiveStateCacheInvalidator): void {
|
|
311
|
+
activeStateCacheInvalidator = invalidator;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function invalidateActiveStateCacheForScope(cwd: string, sessionScope?: string | ActiveSessionScope): void {
|
|
315
|
+
const sessionId = typeof sessionScope === "string" ? sessionScope : sessionScope?.sessionId;
|
|
316
|
+
activeStateCacheInvalidator?.(cwd, sessionId);
|
|
317
|
+
}
|
|
318
|
+
|
|
307
319
|
function activeSnapshotPath(cwd: string, sessionScope?: string | ActiveSessionScope): string {
|
|
308
320
|
return layoutActiveSnapshotPath(cwd, requireSessionId(sessionScope, "activeSnapshotPath"));
|
|
309
321
|
}
|
|
@@ -979,6 +991,7 @@ export async function writeActiveEntry(
|
|
|
979
991
|
persistedSourceRevision(entry) || persistedSourceRevision(await readJsonIfPresent(filePath)) + 1,
|
|
980
992
|
},
|
|
981
993
|
);
|
|
994
|
+
invalidateActiveStateCacheForScope(cwd, sessionScope);
|
|
982
995
|
return filePath;
|
|
983
996
|
}
|
|
984
997
|
|
|
@@ -1003,6 +1016,7 @@ export async function removeActiveEntry(
|
|
|
1003
1016
|
}
|
|
1004
1017
|
const deleted = await atomicRemove(filePath);
|
|
1005
1018
|
if (deleted) await maybeAudit(filePath, options);
|
|
1019
|
+
if (deleted) invalidateActiveStateCacheForScope(cwd, sessionScope);
|
|
1006
1020
|
return { path: filePath, deleted };
|
|
1007
1021
|
},
|
|
1008
1022
|
options?.lock,
|
|
@@ -1049,6 +1063,7 @@ export async function rebuildActiveSnapshot(
|
|
|
1049
1063
|
...entries.map(entry => persistedSourceRevision(entry)),
|
|
1050
1064
|
),
|
|
1051
1065
|
});
|
|
1066
|
+
invalidateActiveStateCacheForScope(cwd, sessionScope);
|
|
1052
1067
|
return snapshotPath;
|
|
1053
1068
|
}
|
|
1054
1069
|
|
|
@@ -204,6 +204,7 @@ export type GjcTeamNotificationDeliveryState =
|
|
|
204
204
|
| "acknowledged";
|
|
205
205
|
|
|
206
206
|
export type GjcTeamPaneAttemptResult = "sent" | "queued" | "deferred" | "failed";
|
|
207
|
+
export type GjcTeamMailboxDeliveryTransportKind = "notifications_sdk" | "pane";
|
|
207
208
|
|
|
208
209
|
export interface GjcTeamNotification {
|
|
209
210
|
id: string;
|
|
@@ -220,6 +221,36 @@ export interface GjcTeamNotification {
|
|
|
220
221
|
updated_at: string;
|
|
221
222
|
replay_count: number;
|
|
222
223
|
}
|
|
224
|
+
export interface GjcTeamMailboxDeliveryInput {
|
|
225
|
+
team_name: string;
|
|
226
|
+
state_dir: string;
|
|
227
|
+
config: GjcTeamConfig;
|
|
228
|
+
notification: GjcTeamNotification;
|
|
229
|
+
message: GjcTeamMailboxMessage;
|
|
230
|
+
cwd: string;
|
|
231
|
+
env: NodeJS.ProcessEnv;
|
|
232
|
+
}
|
|
233
|
+
export type GjcTeamMailboxDeliveryResult =
|
|
234
|
+
| { transport: "notifications_sdk"; state: GjcTeamNotificationDeliveryState; reason?: string }
|
|
235
|
+
| { transport: "pane"; state: GjcTeamPaneAttemptResult; reason?: string };
|
|
236
|
+
export interface GjcTeamMailboxDeliveryTransport {
|
|
237
|
+
deliverMailboxMessage(input: GjcTeamMailboxDeliveryInput): Promise<GjcTeamMailboxDeliveryResult | null>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
let gjcTeamMailboxDeliveryTransport: GjcTeamMailboxDeliveryTransport | undefined;
|
|
241
|
+
|
|
242
|
+
export function setGjcTeamMailboxDeliveryTransport(transport: GjcTeamMailboxDeliveryTransport | undefined): () => void {
|
|
243
|
+
const previous = gjcTeamMailboxDeliveryTransport;
|
|
244
|
+
gjcTeamMailboxDeliveryTransport = transport;
|
|
245
|
+
return () => {
|
|
246
|
+
gjcTeamMailboxDeliveryTransport = previous;
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
export function setGjcTeamMailboxDeliveryTransportForTest(
|
|
250
|
+
transport: GjcTeamMailboxDeliveryTransport | undefined,
|
|
251
|
+
): () => void {
|
|
252
|
+
return setGjcTeamMailboxDeliveryTransport(transport);
|
|
253
|
+
}
|
|
223
254
|
|
|
224
255
|
export interface GjcTeamNotificationSummary {
|
|
225
256
|
total: number;
|
|
@@ -256,6 +287,7 @@ export interface GjcTeamStartOptions {
|
|
|
256
287
|
cwd?: string;
|
|
257
288
|
env?: NodeJS.ProcessEnv;
|
|
258
289
|
dryRun?: boolean;
|
|
290
|
+
mailboxDeliveryTransport?: GjcTeamMailboxDeliveryTransport;
|
|
259
291
|
}
|
|
260
292
|
|
|
261
293
|
export interface GjcTeamApiClaimResult {
|
|
@@ -1056,6 +1088,22 @@ async function readConfig(dir: string): Promise<GjcTeamConfig> {
|
|
|
1056
1088
|
worker_cli_plan: config.worker_cli_plan ?? Array.from({ length: config.worker_count }, () => "gjc"),
|
|
1057
1089
|
};
|
|
1058
1090
|
}
|
|
1091
|
+
const WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS = 100;
|
|
1092
|
+
type WorkerIntegrationConfigCacheEntry = { checkedAt: number; mtimeMs: number; config: GjcTeamConfig };
|
|
1093
|
+
const workerIntegrationConfigCache = new Map<string, WorkerIntegrationConfigCacheEntry>();
|
|
1094
|
+
|
|
1095
|
+
async function readConfigForWorkerIntegration(dir: string): Promise<GjcTeamConfig> {
|
|
1096
|
+
const configPath = path.join(dir, "config.json");
|
|
1097
|
+
const nowMs = Date.now();
|
|
1098
|
+
const stat = await fs.stat(configPath);
|
|
1099
|
+
const cached = workerIntegrationConfigCache.get(configPath);
|
|
1100
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && nowMs - cached.checkedAt <= WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS)
|
|
1101
|
+
return cached.config;
|
|
1102
|
+
const config = await readConfig(dir);
|
|
1103
|
+
workerIntegrationConfigCache.set(configPath, { checkedAt: nowMs, mtimeMs: stat.mtimeMs, config });
|
|
1104
|
+
|
|
1105
|
+
return config;
|
|
1106
|
+
}
|
|
1059
1107
|
async function readPhase(dir: string): Promise<GjcTeamPhase> {
|
|
1060
1108
|
const phase = await readJsonFile<{ current_phase?: GjcTeamPhase }>(path.join(dir, "phase.json"));
|
|
1061
1109
|
return phase?.current_phase ?? "running";
|
|
@@ -1704,6 +1752,23 @@ function runGitResult(cwd: string, args: string[]): GitResult {
|
|
|
1704
1752
|
stderr: result.stderr.toString().trim(),
|
|
1705
1753
|
};
|
|
1706
1754
|
}
|
|
1755
|
+
async function runGitResultAsync(cwd: string, args: string[]): Promise<GitResult> {
|
|
1756
|
+
const result = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
1757
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
1758
|
+
result.exited,
|
|
1759
|
+
new Response(result.stdout).text(),
|
|
1760
|
+
new Response(result.stderr).text(),
|
|
1761
|
+
]);
|
|
1762
|
+
return {
|
|
1763
|
+
ok: exitCode === 0,
|
|
1764
|
+
stdout: stdout.trim(),
|
|
1765
|
+
stderr: stderr.trim(),
|
|
1766
|
+
};
|
|
1767
|
+
}
|
|
1768
|
+
async function tryRunGitAsync(cwd: string, args: string[]): Promise<string | null> {
|
|
1769
|
+
const result = await runGitResultAsync(cwd, args);
|
|
1770
|
+
return result.ok ? result.stdout : null;
|
|
1771
|
+
}
|
|
1707
1772
|
function runGit(cwd: string, args: string[]): string {
|
|
1708
1773
|
const result = runGitResult(cwd, args);
|
|
1709
1774
|
if (result.ok) return result.stdout;
|
|
@@ -2318,6 +2383,9 @@ async function appendCommitHygieneEntries(config: GjcTeamConfig, entries: GjcTea
|
|
|
2318
2383
|
function resolveHead(cwd: string): string | null {
|
|
2319
2384
|
return tryRunGit(cwd, ["rev-parse", "HEAD"]);
|
|
2320
2385
|
}
|
|
2386
|
+
async function resolveHeadAsync(cwd: string): Promise<string | null> {
|
|
2387
|
+
return tryRunGitAsync(cwd, ["rev-parse", "HEAD"]);
|
|
2388
|
+
}
|
|
2321
2389
|
function isAncestor(cwd: string, ancestor: string, descendant: string): boolean {
|
|
2322
2390
|
return runGitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok;
|
|
2323
2391
|
}
|
|
@@ -2337,6 +2405,14 @@ function listConflictFiles(cwd: string): string[] {
|
|
|
2337
2405
|
.map(line => line.trim())
|
|
2338
2406
|
.filter(Boolean);
|
|
2339
2407
|
}
|
|
2408
|
+
async function listConflictFilesAsync(cwd: string): Promise<string[]> {
|
|
2409
|
+
const result = await runGitResultAsync(cwd, ["diff", "--name-only", "--diff-filter=U"]);
|
|
2410
|
+
if (!result.ok || !result.stdout) return [];
|
|
2411
|
+
return result.stdout
|
|
2412
|
+
.split(/\r?\n/)
|
|
2413
|
+
.map(line => line.trim())
|
|
2414
|
+
.filter(Boolean);
|
|
2415
|
+
}
|
|
2340
2416
|
|
|
2341
2417
|
export type GjcWorkerCheckpointClassification =
|
|
2342
2418
|
| { kind: "clean"; files: string[] }
|
|
@@ -2346,17 +2422,14 @@ export type GjcWorkerCheckpointClassification =
|
|
|
2346
2422
|
| { kind: "git_error"; files: string[]; detail: string };
|
|
2347
2423
|
|
|
2348
2424
|
const UNMERGED_GIT_STATUS_CODES = new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
".gjc/_session-*/rlm/",
|
|
2358
|
-
".gjc/_session-*/audit/",
|
|
2359
|
-
];
|
|
2425
|
+
// Every generated/runtime artifact for a GJC session lives under
|
|
2426
|
+
// `.gjc/_session-{id}/...` (see session-layout.ts), so worker auto-checkpoints
|
|
2427
|
+
// exclude the whole session subtree instead of enumerating its subdirectories.
|
|
2428
|
+
// The enumerated form drifted: subtrees outside the list (for example the
|
|
2429
|
+
// extragoal gate receipts from docs/extragoal-skill-template.md, or the
|
|
2430
|
+
// session-root `.session-activity.json` marker) were auto-committed and merged
|
|
2431
|
+
// into the leader branch on projects that do not gitignore `.gjc/_session-*/`.
|
|
2432
|
+
const PROTECTED_WORKER_CHECKPOINT_PREFIXES = [".gjc/_session-*/"];
|
|
2360
2433
|
|
|
2361
2434
|
function parsePorcelainStatusFiles(stdout: string): string[] {
|
|
2362
2435
|
return stdout
|
|
@@ -2407,6 +2480,26 @@ export function classifyWorkerCheckpointStatus(cwd: string): GjcWorkerCheckpoint
|
|
|
2407
2480
|
return { kind: "protected_only", files: classified.protected };
|
|
2408
2481
|
return { kind: "eligible", files: classified.eligible };
|
|
2409
2482
|
}
|
|
2483
|
+
export async function classifyWorkerCheckpointStatusAsync(cwd: string): Promise<GjcWorkerCheckpointClassification> {
|
|
2484
|
+
const status = await runGitResultAsync(cwd, ["status", "--porcelain", "-uall"]);
|
|
2485
|
+
if (!status.ok) {
|
|
2486
|
+
return { kind: "git_error", files: [], detail: status.stderr || status.stdout || "git status failed" };
|
|
2487
|
+
}
|
|
2488
|
+
if (!status.stdout.trim()) return { kind: "clean", files: [] };
|
|
2489
|
+
const files = parsePorcelainStatusFiles(status.stdout);
|
|
2490
|
+
const hasUnmergedStatus = status.stdout
|
|
2491
|
+
.split(/\r?\n/)
|
|
2492
|
+
.filter(Boolean)
|
|
2493
|
+
.some(line => UNMERGED_GIT_STATUS_CODES.has(line.slice(0, 2)));
|
|
2494
|
+
const conflictFiles = await listConflictFilesAsync(cwd);
|
|
2495
|
+
if (hasUnmergedStatus || conflictFiles.length > 0) {
|
|
2496
|
+
return { kind: "conflicted", files: conflictFiles.length > 0 ? conflictFiles : files };
|
|
2497
|
+
}
|
|
2498
|
+
const classified = classifyGjcTeamCheckpointFiles(files);
|
|
2499
|
+
if (classified.eligible.length === 0 && classified.protected.length > 0)
|
|
2500
|
+
return { kind: "protected_only", files: classified.protected };
|
|
2501
|
+
return { kind: "eligible", files: classified.eligible };
|
|
2502
|
+
}
|
|
2410
2503
|
async function appendIntegrationEvent(
|
|
2411
2504
|
dir: string,
|
|
2412
2505
|
type: string,
|
|
@@ -2884,6 +2977,7 @@ async function initializeStateDirs(dir: string, workers: GjcTeamWorker[]): Promi
|
|
|
2884
2977
|
export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTeamSnapshot> {
|
|
2885
2978
|
const cwd = options.cwd ?? process.cwd();
|
|
2886
2979
|
const env = options.env ?? process.env;
|
|
2980
|
+
if (options.mailboxDeliveryTransport) setGjcTeamMailboxDeliveryTransport(options.mailboxDeliveryTransport);
|
|
2887
2981
|
if (!Number.isInteger(options.workerCount) || options.workerCount < 1 || options.workerCount > GJC_TEAM_MAX_WORKERS)
|
|
2888
2982
|
throw new Error(`invalid_team_worker_count:${options.workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
|
|
2889
2983
|
const workerCliPlan = resolveGjcTeamWorkerCliPlan(options.workerCount, env);
|
|
@@ -3065,13 +3159,15 @@ export async function requestGjcWorkerIntegrationAttempt(
|
|
|
3065
3159
|
const worker = env.GJC_TEAM_WORKER_ID?.trim() || env.GJC_TEAM_INTERNAL_WORKER?.split("/").pop()?.trim();
|
|
3066
3160
|
if (!teamName || !worker) return { requested: false, reason: "not_worker" };
|
|
3067
3161
|
const dir = await findTeamDir(teamName, cwd, env);
|
|
3068
|
-
const config = await
|
|
3162
|
+
const config = await readConfigForWorkerIntegration(dir);
|
|
3069
3163
|
const configuredWorker = config.workers.find(candidate => candidate.id === worker);
|
|
3070
3164
|
const worktreePath = env.GJC_TEAM_WORKTREE_PATH?.trim() || configuredWorker?.worktree_path;
|
|
3071
3165
|
if (!worktreePath || !(await pathExists(worktreePath)))
|
|
3072
3166
|
return { requested: false, reason: "missing_worktree", worker, team_name: teamName };
|
|
3073
|
-
const classification =
|
|
3074
|
-
|
|
3167
|
+
const [classification, head] = await Promise.all([
|
|
3168
|
+
classifyWorkerCheckpointStatusAsync(worktreePath),
|
|
3169
|
+
resolveHeadAsync(worktreePath),
|
|
3170
|
+
]);
|
|
3075
3171
|
if (classification.kind === "git_error") {
|
|
3076
3172
|
return { requested: false, reason: "git_error", worker, team_name: teamName, head, status: classification.kind };
|
|
3077
3173
|
}
|
|
@@ -3839,12 +3935,59 @@ async function reconcileTeamNotifications(dir: string, config: GjcTeamConfig): P
|
|
|
3839
3935
|
}
|
|
3840
3936
|
return summarizeNotifications(await listNotificationRecords(dir));
|
|
3841
3937
|
}
|
|
3938
|
+
async function messageForNotification(
|
|
3939
|
+
dir: string,
|
|
3940
|
+
notification: GjcTeamNotification,
|
|
3941
|
+
): Promise<GjcTeamMailboxMessage | null> {
|
|
3942
|
+
if (notification.kind !== "mailbox_message" || notification.source.type !== "message") return null;
|
|
3943
|
+
const mailbox = await readMailbox(dir, notification.recipient);
|
|
3944
|
+
return mailbox.messages.find(message => message.message_id === notification.source.id) ?? null;
|
|
3945
|
+
}
|
|
3946
|
+
|
|
3947
|
+
async function attemptConfiguredMailboxTransport(
|
|
3948
|
+
dir: string,
|
|
3949
|
+
config: GjcTeamConfig,
|
|
3950
|
+
notification: GjcTeamNotification,
|
|
3951
|
+
cwd: string,
|
|
3952
|
+
env: NodeJS.ProcessEnv,
|
|
3953
|
+
): Promise<GjcTeamNotification | null> {
|
|
3954
|
+
if (!gjcTeamMailboxDeliveryTransport) return null;
|
|
3955
|
+
const message = await messageForNotification(dir, notification);
|
|
3956
|
+
if (!message) return null;
|
|
3957
|
+
try {
|
|
3958
|
+
const result = await gjcTeamMailboxDeliveryTransport.deliverMailboxMessage({
|
|
3959
|
+
team_name: config.team_name,
|
|
3960
|
+
state_dir: dir,
|
|
3961
|
+
config,
|
|
3962
|
+
notification,
|
|
3963
|
+
message,
|
|
3964
|
+
cwd,
|
|
3965
|
+
env,
|
|
3966
|
+
});
|
|
3967
|
+
if (!result) return null;
|
|
3968
|
+
if (result.transport === "notifications_sdk" && result.state === "failed") return null;
|
|
3969
|
+
return writeNotificationRecord(dir, {
|
|
3970
|
+
...notification,
|
|
3971
|
+
delivery_state: result.state,
|
|
3972
|
+
pane_attempt_result: result.transport === "pane" ? result.state : undefined,
|
|
3973
|
+
pane_attempt_reason: result.reason ?? result.transport,
|
|
3974
|
+
pane_attempt_at: now(),
|
|
3975
|
+
updated_at: now(),
|
|
3976
|
+
});
|
|
3977
|
+
} catch {
|
|
3978
|
+
return null;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3842
3982
|
async function attemptPaneNotification(
|
|
3843
3983
|
dir: string,
|
|
3844
3984
|
config: GjcTeamConfig,
|
|
3845
3985
|
notification: GjcTeamNotification,
|
|
3846
3986
|
env: NodeJS.ProcessEnv,
|
|
3987
|
+
cwd = process.cwd(),
|
|
3847
3988
|
): Promise<GjcTeamNotification> {
|
|
3989
|
+
const transported = await attemptConfiguredMailboxTransport(dir, config, notification, cwd, env);
|
|
3990
|
+
if (transported) return transported;
|
|
3848
3991
|
const paneId =
|
|
3849
3992
|
notification.recipient === "leader-fixed"
|
|
3850
3993
|
? config.leader.pane_id
|
|
@@ -3891,6 +4034,7 @@ export async function replayGjcTeamNotifications(
|
|
|
3891
4034
|
replay_count: (notification.replay_count ?? 0) + 1,
|
|
3892
4035
|
},
|
|
3893
4036
|
env,
|
|
4037
|
+
cwd,
|
|
3894
4038
|
);
|
|
3895
4039
|
next.push(attempted);
|
|
3896
4040
|
}
|
|
@@ -3919,8 +4063,13 @@ export async function sendGjcTeamMessage(
|
|
|
3919
4063
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
3920
4064
|
};
|
|
3921
4065
|
const written = await writeMailboxMessage(dir, toWorker, message);
|
|
4066
|
+
const existingNotification = await readJsonFile<GjcTeamNotification>(
|
|
4067
|
+
notificationPath(dir, messageNotificationId(config.team_name, toWorker, written.message_id)),
|
|
4068
|
+
);
|
|
3922
4069
|
const notification = await createMessageNotification(dir, config.team_name, written);
|
|
3923
|
-
|
|
4070
|
+
if (!existingNotification) {
|
|
4071
|
+
await attemptPaneNotification(dir, config, notification, env, cwd);
|
|
4072
|
+
}
|
|
3924
4073
|
await appendEvent(dir, {
|
|
3925
4074
|
type: "message_sent",
|
|
3926
4075
|
worker: fromWorker,
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { DEFAULT_ULTRAGOAL_OBJECTIVE } from "./goal-mode-request";
|
|
3
3
|
import { resolveGjcSessionForRead, SessionResolutionError } from "./session-resolution";
|
|
4
|
+
import { validateDeferredMemberReceiptFresh, validateReceiptFreshBase } from "./ultragoal-receipt-freshness";
|
|
4
5
|
import {
|
|
5
|
-
computeUltragoalPlanGeneration,
|
|
6
6
|
getUltragoalPaths,
|
|
7
7
|
getUltragoalRunCompletionState,
|
|
8
|
-
hashStructuredValue,
|
|
9
8
|
readUltragoalLedger,
|
|
10
9
|
readUltragoalPlan,
|
|
11
10
|
recordUltragoalNudgeIfBudgetRemaining,
|
|
12
11
|
resolveUltragoalNudgeBudget,
|
|
13
12
|
selectUltragoalNudgeTarget,
|
|
14
|
-
type UltragoalCompletionVerification,
|
|
15
13
|
type UltragoalGoal,
|
|
16
14
|
type UltragoalLedgerEvent,
|
|
17
15
|
type UltragoalNudgeSurface,
|
|
@@ -176,26 +174,6 @@ function findReceiptGoal(
|
|
|
176
174
|
return storyGoal ? { goal: storyGoal, receiptKind: "per-goal" } : null;
|
|
177
175
|
}
|
|
178
176
|
|
|
179
|
-
function findLedgerReceiptEvent(
|
|
180
|
-
ledger: readonly UltragoalLedgerEvent[],
|
|
181
|
-
receipt: UltragoalCompletionVerification,
|
|
182
|
-
): UltragoalLedgerEvent | null {
|
|
183
|
-
return (
|
|
184
|
-
ledger.find(event => {
|
|
185
|
-
if (event.eventId !== receipt.checkpointLedgerEventId) return false;
|
|
186
|
-
if (event.event !== "goal_checkpointed") return false;
|
|
187
|
-
if (event.goalId !== receipt.goalId) return false;
|
|
188
|
-
const eventReceipt = event.completionVerification as UltragoalCompletionVerification | undefined;
|
|
189
|
-
return (
|
|
190
|
-
event.status === "complete" &&
|
|
191
|
-
eventReceipt?.receiptId === receipt.receiptId &&
|
|
192
|
-
eventReceipt.receiptKind === receipt.receiptKind &&
|
|
193
|
-
eventReceipt.planGeneration === receipt.planGeneration
|
|
194
|
-
);
|
|
195
|
-
}) ?? null
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
177
|
export function validateCompletionReceipt(input: {
|
|
200
178
|
plan: UltragoalPlan;
|
|
201
179
|
ledger: readonly UltragoalLedgerEvent[];
|
|
@@ -210,55 +188,66 @@ export function validateCompletionReceipt(input: {
|
|
|
210
188
|
goalId: input.goal.id,
|
|
211
189
|
};
|
|
212
190
|
}
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
message: `Ultragoal ${input.goal.id} receipt is malformed or stale.`,
|
|
223
|
-
goalId: input.goal.id,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
const event = findLedgerReceiptEvent(input.ledger, receipt);
|
|
227
|
-
if (!event) {
|
|
228
|
-
return {
|
|
229
|
-
state: "active_stale_receipt",
|
|
230
|
-
message: `Ultragoal ${input.goal.id} receipt ledger event is missing.`,
|
|
231
|
-
goalId: input.goal.id,
|
|
232
|
-
};
|
|
191
|
+
if (receipt.validationBatch?.role === "deferred-member") {
|
|
192
|
+
return validateDeferredMemberReceiptFresh({
|
|
193
|
+
plan: input.plan,
|
|
194
|
+
ledger: input.ledger,
|
|
195
|
+
goal: input.goal,
|
|
196
|
+
receipt,
|
|
197
|
+
receiptKind: input.receiptKind,
|
|
198
|
+
requireClose: true,
|
|
199
|
+
});
|
|
233
200
|
}
|
|
234
|
-
const
|
|
201
|
+
const baseDiagnostic = validateReceiptFreshBase({
|
|
235
202
|
plan: input.plan,
|
|
236
203
|
ledger: input.ledger,
|
|
237
204
|
goal: input.goal,
|
|
205
|
+
receipt,
|
|
238
206
|
receiptKind: input.receiptKind,
|
|
239
|
-
beforeStatus: receipt.goalStatusBeforeCheckpoint,
|
|
240
|
-
excludeEventId: receipt.checkpointLedgerEventId,
|
|
241
207
|
});
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
208
|
+
if (baseDiagnostic) return baseDiagnostic;
|
|
209
|
+
if (receipt.validationBatch?.role === "batch-close") {
|
|
210
|
+
for (const memberId of receipt.validationBatch.memberIds) {
|
|
211
|
+
const member = input.plan.goals.find(goal => goal.id === memberId);
|
|
212
|
+
if (
|
|
213
|
+
!member?.validationBatch ||
|
|
214
|
+
member.validationBatch.metadataHash !== receipt.validationBatch.memberMetadataHashes[memberId]
|
|
215
|
+
) {
|
|
216
|
+
return {
|
|
217
|
+
state: "active_stale_receipt",
|
|
218
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt has stale member metadata for ${memberId}.`,
|
|
219
|
+
goalId: input.goal.id,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (memberId === receipt.validationBatch.finalGoalId) continue;
|
|
223
|
+
const memberReceipt = member.completionVerification;
|
|
224
|
+
if (memberReceipt?.validationBatch?.role !== "deferred-member") {
|
|
225
|
+
return {
|
|
226
|
+
state: "active_missing_final_receipt",
|
|
227
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt requires deferred member receipt for ${memberId}.`,
|
|
228
|
+
goalId: input.goal.id,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
const memberDiagnostic = validateDeferredMemberReceiptFresh({
|
|
232
|
+
plan: input.plan,
|
|
233
|
+
ledger: input.ledger,
|
|
234
|
+
goal: member,
|
|
235
|
+
receipt: memberReceipt,
|
|
236
|
+
receiptKind: "per-goal",
|
|
237
|
+
requireClose: false,
|
|
238
|
+
});
|
|
239
|
+
if (memberDiagnostic.state !== "active_verified_complete") return memberDiagnostic;
|
|
240
|
+
if (
|
|
241
|
+
receipt.validationBatch.memberReceiptIds[memberId] !== memberReceipt.receiptId ||
|
|
242
|
+
receipt.validationBatch.memberChangeSetHashes[memberId] !== memberReceipt.validationBatch.changeSetHash
|
|
243
|
+
) {
|
|
244
|
+
return {
|
|
245
|
+
state: "active_stale_receipt",
|
|
246
|
+
message: `Ultragoal ${input.goal.id} batch-close receipt is stale for deferred member ${memberId}.`,
|
|
247
|
+
goalId: input.goal.id,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
262
251
|
}
|
|
263
252
|
if (input.receiptKind === "final-aggregate") {
|
|
264
253
|
const incomplete = requiredGoals(input.plan).filter(goal => goal.status !== "complete");
|