@gajae-code/coding-agent 0.8.2 → 0.9.1
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 +53 -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 +119 -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/tmux-common.d.ts +1 -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/components/thinking-selector.d.ts +2 -2
- 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/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +4 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -3
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +3 -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 +3 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -0
- package/dist/types/notifications/config-commands.d.ts +9 -0
- package/dist/types/notifications/config.d.ts +22 -0
- package/dist/types/notifications/index.d.ts +2 -0
- package/dist/types/notifications/lifecycle-commands.d.ts +1 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +2 -1
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +3 -3
- package/dist/types/notifications/telegram-daemon.d.ts +30 -0
- package/dist/types/notifications/telegram-reference.d.ts +7 -0
- package/dist/types/notifications/threaded-render.d.ts +14 -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/utils/pasted-image-path.d.ts +30 -0
- package/dist/types/web/scrapers/youtube.d.ts +8 -0
- package/package.json +8 -11
- 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/notify-cli.ts +11 -9
- 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/keybindings.ts +1 -1
- package/src/config/settings-schema.ts +76 -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/launch-tmux.ts +6 -0
- 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 +175 -19
- package/src/gjc-runtime/tmux-common.ts +1 -0
- 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 +1602 -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 +18 -9
- package/src/lsp/client.ts +1 -0
- 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/footer.ts +7 -2
- package/src/modes/components/status-line.ts +167 -54
- package/src/modes/components/thinking-selector.ts +10 -8
- 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 +77 -49
- package/src/modes/controllers/extension-ui-controller.ts +8 -1
- package/src/modes/controllers/input-controller.ts +13 -18
- package/src/modes/controllers/selector-controller.ts +41 -0
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/rpc/rpc-mode.ts +32 -12
- package/src/modes/rpc/rpc-socket-security.ts +13 -1
- 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 +3 -0
- package/src/modes/utils/injected-user-submission.ts +94 -0
- package/src/modes/utils/ui-helpers.ts +17 -4
- package/src/notifications/config-commands.ts +20 -0
- package/src/notifications/config.ts +43 -4
- package/src/notifications/index.ts +105 -9
- package/src/notifications/lifecycle-commands.ts +37 -16
- package/src/notifications/lifecycle-control-runtime.ts +8 -3
- package/src/notifications/reply-sent-store.ts +134 -0
- package/src/notifications/rich-draft.ts +107 -0
- package/src/notifications/rich-render.ts +142 -0
- package/src/notifications/telegram-daemon-cli.ts +54 -7
- package/src/notifications/telegram-daemon-control.ts +2 -2
- package/src/notifications/telegram-daemon.ts +390 -56
- package/src/notifications/telegram-reference.ts +17 -0
- package/src/notifications/threaded-render.ts +45 -2
- 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 +6 -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 +68 -30
- package/src/session/agent-session.ts +734 -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 +143 -1
- 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/utils/pasted-image-path.ts +170 -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
|
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
import {
|
|
23
23
|
buildGjcTmuxExactOptionTarget,
|
|
24
24
|
buildGjcTmuxUntaggedSessionHint,
|
|
25
|
+
GJC_TMUX_ACTIVE_SESSION_ENV,
|
|
25
26
|
GJC_TMUX_PROFILE_OPTION,
|
|
26
27
|
GJC_TMUX_PROFILE_VALUE,
|
|
27
28
|
resolveGjcTmuxCommand,
|
|
@@ -204,6 +205,7 @@ export type GjcTeamNotificationDeliveryState =
|
|
|
204
205
|
| "acknowledged";
|
|
205
206
|
|
|
206
207
|
export type GjcTeamPaneAttemptResult = "sent" | "queued" | "deferred" | "failed";
|
|
208
|
+
export type GjcTeamMailboxDeliveryTransportKind = "notifications_sdk" | "pane";
|
|
207
209
|
|
|
208
210
|
export interface GjcTeamNotification {
|
|
209
211
|
id: string;
|
|
@@ -220,6 +222,36 @@ export interface GjcTeamNotification {
|
|
|
220
222
|
updated_at: string;
|
|
221
223
|
replay_count: number;
|
|
222
224
|
}
|
|
225
|
+
export interface GjcTeamMailboxDeliveryInput {
|
|
226
|
+
team_name: string;
|
|
227
|
+
state_dir: string;
|
|
228
|
+
config: GjcTeamConfig;
|
|
229
|
+
notification: GjcTeamNotification;
|
|
230
|
+
message: GjcTeamMailboxMessage;
|
|
231
|
+
cwd: string;
|
|
232
|
+
env: NodeJS.ProcessEnv;
|
|
233
|
+
}
|
|
234
|
+
export type GjcTeamMailboxDeliveryResult =
|
|
235
|
+
| { transport: "notifications_sdk"; state: GjcTeamNotificationDeliveryState; reason?: string }
|
|
236
|
+
| { transport: "pane"; state: GjcTeamPaneAttemptResult; reason?: string };
|
|
237
|
+
export interface GjcTeamMailboxDeliveryTransport {
|
|
238
|
+
deliverMailboxMessage(input: GjcTeamMailboxDeliveryInput): Promise<GjcTeamMailboxDeliveryResult | null>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let gjcTeamMailboxDeliveryTransport: GjcTeamMailboxDeliveryTransport | undefined;
|
|
242
|
+
|
|
243
|
+
export function setGjcTeamMailboxDeliveryTransport(transport: GjcTeamMailboxDeliveryTransport | undefined): () => void {
|
|
244
|
+
const previous = gjcTeamMailboxDeliveryTransport;
|
|
245
|
+
gjcTeamMailboxDeliveryTransport = transport;
|
|
246
|
+
return () => {
|
|
247
|
+
gjcTeamMailboxDeliveryTransport = previous;
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
export function setGjcTeamMailboxDeliveryTransportForTest(
|
|
251
|
+
transport: GjcTeamMailboxDeliveryTransport | undefined,
|
|
252
|
+
): () => void {
|
|
253
|
+
return setGjcTeamMailboxDeliveryTransport(transport);
|
|
254
|
+
}
|
|
223
255
|
|
|
224
256
|
export interface GjcTeamNotificationSummary {
|
|
225
257
|
total: number;
|
|
@@ -256,6 +288,7 @@ export interface GjcTeamStartOptions {
|
|
|
256
288
|
cwd?: string;
|
|
257
289
|
env?: NodeJS.ProcessEnv;
|
|
258
290
|
dryRun?: boolean;
|
|
291
|
+
mailboxDeliveryTransport?: GjcTeamMailboxDeliveryTransport;
|
|
259
292
|
}
|
|
260
293
|
|
|
261
294
|
export interface GjcTeamApiClaimResult {
|
|
@@ -1056,6 +1089,22 @@ async function readConfig(dir: string): Promise<GjcTeamConfig> {
|
|
|
1056
1089
|
worker_cli_plan: config.worker_cli_plan ?? Array.from({ length: config.worker_count }, () => "gjc"),
|
|
1057
1090
|
};
|
|
1058
1091
|
}
|
|
1092
|
+
const WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS = 100;
|
|
1093
|
+
type WorkerIntegrationConfigCacheEntry = { checkedAt: number; mtimeMs: number; config: GjcTeamConfig };
|
|
1094
|
+
const workerIntegrationConfigCache = new Map<string, WorkerIntegrationConfigCacheEntry>();
|
|
1095
|
+
|
|
1096
|
+
async function readConfigForWorkerIntegration(dir: string): Promise<GjcTeamConfig> {
|
|
1097
|
+
const configPath = path.join(dir, "config.json");
|
|
1098
|
+
const nowMs = Date.now();
|
|
1099
|
+
const stat = await fs.stat(configPath);
|
|
1100
|
+
const cached = workerIntegrationConfigCache.get(configPath);
|
|
1101
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && nowMs - cached.checkedAt <= WORKER_INTEGRATION_CONFIG_CACHE_TTL_MS)
|
|
1102
|
+
return cached.config;
|
|
1103
|
+
const config = await readConfig(dir);
|
|
1104
|
+
workerIntegrationConfigCache.set(configPath, { checkedAt: nowMs, mtimeMs: stat.mtimeMs, config });
|
|
1105
|
+
|
|
1106
|
+
return config;
|
|
1107
|
+
}
|
|
1059
1108
|
async function readPhase(dir: string): Promise<GjcTeamPhase> {
|
|
1060
1109
|
const phase = await readJsonFile<{ current_phase?: GjcTeamPhase }>(path.join(dir, "phase.json"));
|
|
1061
1110
|
return phase?.current_phase ?? "running";
|
|
@@ -1704,6 +1753,23 @@ function runGitResult(cwd: string, args: string[]): GitResult {
|
|
|
1704
1753
|
stderr: result.stderr.toString().trim(),
|
|
1705
1754
|
};
|
|
1706
1755
|
}
|
|
1756
|
+
async function runGitResultAsync(cwd: string, args: string[]): Promise<GitResult> {
|
|
1757
|
+
const result = Bun.spawn(["git", ...args], { cwd, stdout: "pipe", stderr: "pipe" });
|
|
1758
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
1759
|
+
result.exited,
|
|
1760
|
+
new Response(result.stdout).text(),
|
|
1761
|
+
new Response(result.stderr).text(),
|
|
1762
|
+
]);
|
|
1763
|
+
return {
|
|
1764
|
+
ok: exitCode === 0,
|
|
1765
|
+
stdout: stdout.trim(),
|
|
1766
|
+
stderr: stderr.trim(),
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
async function tryRunGitAsync(cwd: string, args: string[]): Promise<string | null> {
|
|
1770
|
+
const result = await runGitResultAsync(cwd, args);
|
|
1771
|
+
return result.ok ? result.stdout : null;
|
|
1772
|
+
}
|
|
1707
1773
|
function runGit(cwd: string, args: string[]): string {
|
|
1708
1774
|
const result = runGitResult(cwd, args);
|
|
1709
1775
|
if (result.ok) return result.stdout;
|
|
@@ -1857,16 +1923,22 @@ function tagTmuxSessionAsGjcLeader(tmuxCommand: string, sessionName: string): bo
|
|
|
1857
1923
|
function readCurrentTmuxLeaderContext(tmuxCommand: string, env: NodeJS.ProcessEnv): GjcTmuxLeaderContext {
|
|
1858
1924
|
if (Bun.which(tmuxCommand) === null)
|
|
1859
1925
|
throw new Error(buildTeamTmuxLeaderRequirementMessage(`tmux_not_installed:${tmuxCommand}`));
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1926
|
+
// Prefer the explicit GJC-managed session name propagated by `gjc --tmux`
|
|
1927
|
+
// (GJC_TMUX_ACTIVE_SESSION). Under psmux on Windows the inherited TMUX_PANE
|
|
1928
|
+
// can resolve to the wrong/default session, so querying the tagged session
|
|
1929
|
+
// by name is authoritative for GJC-launched leaders. Fall back to TMUX_PANE,
|
|
1930
|
+
// then to the ambient session, to keep native tmux/WSL flows unchanged.
|
|
1931
|
+
const activeSession = env[GJC_TMUX_ACTIVE_SESSION_ENV]?.trim();
|
|
1932
|
+
const displayTarget = activeSession ? buildGjcTmuxExactOptionTarget(activeSession, { env }) : env.TMUX_PANE?.trim();
|
|
1933
|
+
const args = displayTarget
|
|
1934
|
+
? ["display-message", "-p", "-t", displayTarget, "#S:#I #{pane_id}"]
|
|
1863
1935
|
: ["display-message", "-p", "#S:#I #{pane_id}"];
|
|
1864
1936
|
const result = Bun.spawnSync([tmuxCommand, ...args], { stdout: "pipe", stderr: "pipe" });
|
|
1865
1937
|
if (result.exitCode !== 0) {
|
|
1866
1938
|
// Distinguish "you are not inside any tmux session" from a genuine tmux
|
|
1867
1939
|
// query failure so the caller gets actionable guidance instead of raw
|
|
1868
1940
|
// tmux stderr. `gjc team` needs a tmux leader; outside tmux there is none.
|
|
1869
|
-
const insideTmux = Boolean(env.TMUX?.trim() || env.TMUX_PANE?.trim());
|
|
1941
|
+
const insideTmux = Boolean(env.TMUX?.trim() || env.TMUX_PANE?.trim() || activeSession);
|
|
1870
1942
|
const stderr = result.stderr.toString().trim();
|
|
1871
1943
|
throw new Error(
|
|
1872
1944
|
buildTeamTmuxLeaderRequirementMessage(
|
|
@@ -2318,6 +2390,9 @@ async function appendCommitHygieneEntries(config: GjcTeamConfig, entries: GjcTea
|
|
|
2318
2390
|
function resolveHead(cwd: string): string | null {
|
|
2319
2391
|
return tryRunGit(cwd, ["rev-parse", "HEAD"]);
|
|
2320
2392
|
}
|
|
2393
|
+
async function resolveHeadAsync(cwd: string): Promise<string | null> {
|
|
2394
|
+
return tryRunGitAsync(cwd, ["rev-parse", "HEAD"]);
|
|
2395
|
+
}
|
|
2321
2396
|
function isAncestor(cwd: string, ancestor: string, descendant: string): boolean {
|
|
2322
2397
|
return runGitResult(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]).ok;
|
|
2323
2398
|
}
|
|
@@ -2337,6 +2412,14 @@ function listConflictFiles(cwd: string): string[] {
|
|
|
2337
2412
|
.map(line => line.trim())
|
|
2338
2413
|
.filter(Boolean);
|
|
2339
2414
|
}
|
|
2415
|
+
async function listConflictFilesAsync(cwd: string): Promise<string[]> {
|
|
2416
|
+
const result = await runGitResultAsync(cwd, ["diff", "--name-only", "--diff-filter=U"]);
|
|
2417
|
+
if (!result.ok || !result.stdout) return [];
|
|
2418
|
+
return result.stdout
|
|
2419
|
+
.split(/\r?\n/)
|
|
2420
|
+
.map(line => line.trim())
|
|
2421
|
+
.filter(Boolean);
|
|
2422
|
+
}
|
|
2340
2423
|
|
|
2341
2424
|
export type GjcWorkerCheckpointClassification =
|
|
2342
2425
|
| { kind: "clean"; files: string[] }
|
|
@@ -2346,17 +2429,14 @@ export type GjcWorkerCheckpointClassification =
|
|
|
2346
2429
|
| { kind: "git_error"; files: string[]; detail: string };
|
|
2347
2430
|
|
|
2348
2431
|
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
|
-
];
|
|
2432
|
+
// Every generated/runtime artifact for a GJC session lives under
|
|
2433
|
+
// `.gjc/_session-{id}/...` (see session-layout.ts), so worker auto-checkpoints
|
|
2434
|
+
// exclude the whole session subtree instead of enumerating its subdirectories.
|
|
2435
|
+
// The enumerated form drifted: subtrees outside the list (for example the
|
|
2436
|
+
// extragoal gate receipts from docs/extragoal-skill-template.md, or the
|
|
2437
|
+
// session-root `.session-activity.json` marker) were auto-committed and merged
|
|
2438
|
+
// into the leader branch on projects that do not gitignore `.gjc/_session-*/`.
|
|
2439
|
+
const PROTECTED_WORKER_CHECKPOINT_PREFIXES = [".gjc/_session-*/"];
|
|
2360
2440
|
|
|
2361
2441
|
function parsePorcelainStatusFiles(stdout: string): string[] {
|
|
2362
2442
|
return stdout
|
|
@@ -2407,6 +2487,26 @@ export function classifyWorkerCheckpointStatus(cwd: string): GjcWorkerCheckpoint
|
|
|
2407
2487
|
return { kind: "protected_only", files: classified.protected };
|
|
2408
2488
|
return { kind: "eligible", files: classified.eligible };
|
|
2409
2489
|
}
|
|
2490
|
+
export async function classifyWorkerCheckpointStatusAsync(cwd: string): Promise<GjcWorkerCheckpointClassification> {
|
|
2491
|
+
const status = await runGitResultAsync(cwd, ["status", "--porcelain", "-uall"]);
|
|
2492
|
+
if (!status.ok) {
|
|
2493
|
+
return { kind: "git_error", files: [], detail: status.stderr || status.stdout || "git status failed" };
|
|
2494
|
+
}
|
|
2495
|
+
if (!status.stdout.trim()) return { kind: "clean", files: [] };
|
|
2496
|
+
const files = parsePorcelainStatusFiles(status.stdout);
|
|
2497
|
+
const hasUnmergedStatus = status.stdout
|
|
2498
|
+
.split(/\r?\n/)
|
|
2499
|
+
.filter(Boolean)
|
|
2500
|
+
.some(line => UNMERGED_GIT_STATUS_CODES.has(line.slice(0, 2)));
|
|
2501
|
+
const conflictFiles = await listConflictFilesAsync(cwd);
|
|
2502
|
+
if (hasUnmergedStatus || conflictFiles.length > 0) {
|
|
2503
|
+
return { kind: "conflicted", files: conflictFiles.length > 0 ? conflictFiles : files };
|
|
2504
|
+
}
|
|
2505
|
+
const classified = classifyGjcTeamCheckpointFiles(files);
|
|
2506
|
+
if (classified.eligible.length === 0 && classified.protected.length > 0)
|
|
2507
|
+
return { kind: "protected_only", files: classified.protected };
|
|
2508
|
+
return { kind: "eligible", files: classified.eligible };
|
|
2509
|
+
}
|
|
2410
2510
|
async function appendIntegrationEvent(
|
|
2411
2511
|
dir: string,
|
|
2412
2512
|
type: string,
|
|
@@ -2884,6 +2984,7 @@ async function initializeStateDirs(dir: string, workers: GjcTeamWorker[]): Promi
|
|
|
2884
2984
|
export async function startGjcTeam(options: GjcTeamStartOptions): Promise<GjcTeamSnapshot> {
|
|
2885
2985
|
const cwd = options.cwd ?? process.cwd();
|
|
2886
2986
|
const env = options.env ?? process.env;
|
|
2987
|
+
if (options.mailboxDeliveryTransport) setGjcTeamMailboxDeliveryTransport(options.mailboxDeliveryTransport);
|
|
2887
2988
|
if (!Number.isInteger(options.workerCount) || options.workerCount < 1 || options.workerCount > GJC_TEAM_MAX_WORKERS)
|
|
2888
2989
|
throw new Error(`invalid_team_worker_count:${options.workerCount}:expected_1_${GJC_TEAM_MAX_WORKERS}`);
|
|
2889
2990
|
const workerCliPlan = resolveGjcTeamWorkerCliPlan(options.workerCount, env);
|
|
@@ -3065,13 +3166,15 @@ export async function requestGjcWorkerIntegrationAttempt(
|
|
|
3065
3166
|
const worker = env.GJC_TEAM_WORKER_ID?.trim() || env.GJC_TEAM_INTERNAL_WORKER?.split("/").pop()?.trim();
|
|
3066
3167
|
if (!teamName || !worker) return { requested: false, reason: "not_worker" };
|
|
3067
3168
|
const dir = await findTeamDir(teamName, cwd, env);
|
|
3068
|
-
const config = await
|
|
3169
|
+
const config = await readConfigForWorkerIntegration(dir);
|
|
3069
3170
|
const configuredWorker = config.workers.find(candidate => candidate.id === worker);
|
|
3070
3171
|
const worktreePath = env.GJC_TEAM_WORKTREE_PATH?.trim() || configuredWorker?.worktree_path;
|
|
3071
3172
|
if (!worktreePath || !(await pathExists(worktreePath)))
|
|
3072
3173
|
return { requested: false, reason: "missing_worktree", worker, team_name: teamName };
|
|
3073
|
-
const classification =
|
|
3074
|
-
|
|
3174
|
+
const [classification, head] = await Promise.all([
|
|
3175
|
+
classifyWorkerCheckpointStatusAsync(worktreePath),
|
|
3176
|
+
resolveHeadAsync(worktreePath),
|
|
3177
|
+
]);
|
|
3075
3178
|
if (classification.kind === "git_error") {
|
|
3076
3179
|
return { requested: false, reason: "git_error", worker, team_name: teamName, head, status: classification.kind };
|
|
3077
3180
|
}
|
|
@@ -3839,12 +3942,59 @@ async function reconcileTeamNotifications(dir: string, config: GjcTeamConfig): P
|
|
|
3839
3942
|
}
|
|
3840
3943
|
return summarizeNotifications(await listNotificationRecords(dir));
|
|
3841
3944
|
}
|
|
3945
|
+
async function messageForNotification(
|
|
3946
|
+
dir: string,
|
|
3947
|
+
notification: GjcTeamNotification,
|
|
3948
|
+
): Promise<GjcTeamMailboxMessage | null> {
|
|
3949
|
+
if (notification.kind !== "mailbox_message" || notification.source.type !== "message") return null;
|
|
3950
|
+
const mailbox = await readMailbox(dir, notification.recipient);
|
|
3951
|
+
return mailbox.messages.find(message => message.message_id === notification.source.id) ?? null;
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
async function attemptConfiguredMailboxTransport(
|
|
3955
|
+
dir: string,
|
|
3956
|
+
config: GjcTeamConfig,
|
|
3957
|
+
notification: GjcTeamNotification,
|
|
3958
|
+
cwd: string,
|
|
3959
|
+
env: NodeJS.ProcessEnv,
|
|
3960
|
+
): Promise<GjcTeamNotification | null> {
|
|
3961
|
+
if (!gjcTeamMailboxDeliveryTransport) return null;
|
|
3962
|
+
const message = await messageForNotification(dir, notification);
|
|
3963
|
+
if (!message) return null;
|
|
3964
|
+
try {
|
|
3965
|
+
const result = await gjcTeamMailboxDeliveryTransport.deliverMailboxMessage({
|
|
3966
|
+
team_name: config.team_name,
|
|
3967
|
+
state_dir: dir,
|
|
3968
|
+
config,
|
|
3969
|
+
notification,
|
|
3970
|
+
message,
|
|
3971
|
+
cwd,
|
|
3972
|
+
env,
|
|
3973
|
+
});
|
|
3974
|
+
if (!result) return null;
|
|
3975
|
+
if (result.transport === "notifications_sdk" && result.state === "failed") return null;
|
|
3976
|
+
return writeNotificationRecord(dir, {
|
|
3977
|
+
...notification,
|
|
3978
|
+
delivery_state: result.state,
|
|
3979
|
+
pane_attempt_result: result.transport === "pane" ? result.state : undefined,
|
|
3980
|
+
pane_attempt_reason: result.reason ?? result.transport,
|
|
3981
|
+
pane_attempt_at: now(),
|
|
3982
|
+
updated_at: now(),
|
|
3983
|
+
});
|
|
3984
|
+
} catch {
|
|
3985
|
+
return null;
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
|
|
3842
3989
|
async function attemptPaneNotification(
|
|
3843
3990
|
dir: string,
|
|
3844
3991
|
config: GjcTeamConfig,
|
|
3845
3992
|
notification: GjcTeamNotification,
|
|
3846
3993
|
env: NodeJS.ProcessEnv,
|
|
3994
|
+
cwd = process.cwd(),
|
|
3847
3995
|
): Promise<GjcTeamNotification> {
|
|
3996
|
+
const transported = await attemptConfiguredMailboxTransport(dir, config, notification, cwd, env);
|
|
3997
|
+
if (transported) return transported;
|
|
3848
3998
|
const paneId =
|
|
3849
3999
|
notification.recipient === "leader-fixed"
|
|
3850
4000
|
? config.leader.pane_id
|
|
@@ -3891,6 +4041,7 @@ export async function replayGjcTeamNotifications(
|
|
|
3891
4041
|
replay_count: (notification.replay_count ?? 0) + 1,
|
|
3892
4042
|
},
|
|
3893
4043
|
env,
|
|
4044
|
+
cwd,
|
|
3894
4045
|
);
|
|
3895
4046
|
next.push(attempted);
|
|
3896
4047
|
}
|
|
@@ -3919,8 +4070,13 @@ export async function sendGjcTeamMessage(
|
|
|
3919
4070
|
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}),
|
|
3920
4071
|
};
|
|
3921
4072
|
const written = await writeMailboxMessage(dir, toWorker, message);
|
|
4073
|
+
const existingNotification = await readJsonFile<GjcTeamNotification>(
|
|
4074
|
+
notificationPath(dir, messageNotificationId(config.team_name, toWorker, written.message_id)),
|
|
4075
|
+
);
|
|
3922
4076
|
const notification = await createMessageNotification(dir, config.team_name, written);
|
|
3923
|
-
|
|
4077
|
+
if (!existingNotification) {
|
|
4078
|
+
await attemptPaneNotification(dir, config, notification, env, cwd);
|
|
4079
|
+
}
|
|
3924
4080
|
await appendEvent(dir, {
|
|
3925
4081
|
type: "message_sent",
|
|
3926
4082
|
worker: fromWorker,
|
|
@@ -4,6 +4,7 @@ import { resolveGjcTmuxBinary } from "./psmux-detect";
|
|
|
4
4
|
export const GJC_DEFAULT_TMUX_SESSION = "gajae_code";
|
|
5
5
|
export const GJC_TMUX_SESSION_PREFIX = `${GJC_DEFAULT_TMUX_SESSION}_`;
|
|
6
6
|
export const GJC_TMUX_COMMAND_ENV = "GJC_TMUX_COMMAND";
|
|
7
|
+
export const GJC_TMUX_ACTIVE_SESSION_ENV = "GJC_TMUX_ACTIVE_SESSION";
|
|
7
8
|
export const GJC_TMUX_PROFILE_ENV = "GJC_TMUX_PROFILE";
|
|
8
9
|
export const GJC_TMUX_MOUSE_ENV = "GJC_MOUSE";
|
|
9
10
|
export const GJC_TMUX_PROFILE_OPTION = "@gjc-profile";
|
|
@@ -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");
|