@gajae-code/coding-agent 0.15.6 → 0.16.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 +99 -0
- package/dist/types/async/job-manager.d.ts +32 -1
- package/dist/types/browser-backend.d.ts +12 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +14 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/sdk.d.ts +2 -0
- package/dist/types/config/autorouting-tier-map.d.ts +24 -0
- package/dist/types/config/billing-path.d.ts +25 -0
- package/dist/types/config/config-schema-version.d.ts +13 -1
- package/dist/types/config/file-lock.d.ts +5 -0
- package/dist/types/config/model-registry.d.ts +22 -0
- package/dist/types/config/settings-schema.d.ts +27 -8
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/projection-scan.d.ts +8 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -3
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/launch-worktree.d.ts +25 -1
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +22 -0
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +66 -1
- package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +1 -0
- package/dist/types/gjc-runtime/tmux-sessions.d.ts +27 -0
- package/dist/types/main.d.ts +1 -0
- package/dist/types/master-mode/context.d.ts +21 -0
- package/dist/types/master-mode/first-request.d.ts +34 -0
- package/dist/types/master-mode/snapshot.d.ts +32 -0
- package/dist/types/modes/acp/acp-agent.d.ts +21 -21
- package/dist/types/modes/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/model-selector.d.ts +4 -0
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/sessions-dashboard.d.ts +2 -1
- package/dist/types/modes/components/status-line/model-name.d.ts +18 -0
- package/dist/types/modes/components/status-line/priority-row.d.ts +34 -0
- package/dist/types/modes/components/status-line/segments.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -2
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +79 -2
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/ensure.d.ts +3 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle-ledger.d.ts +6 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +40 -0
- package/dist/types/sdk/broker/master-capability.d.ts +10 -0
- package/dist/types/sdk/broker/session-index.d.ts +40 -11
- package/dist/types/sdk/broker/session-scope.d.ts +72 -0
- package/dist/types/sdk/broker/spawn-authority.d.ts +201 -0
- package/dist/types/sdk/broker/spawn-substrate.d.ts +30 -0
- package/dist/types/sdk/broker/startup-budget.d.ts +52 -0
- package/dist/types/sdk/broker/state-version.d.ts +21 -1
- package/dist/types/sdk/broker/worktree-occupancy.d.ts +28 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +3 -0
- package/dist/types/sdk/bus/discord-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/index.d.ts +9 -1
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-conversation.d.ts +1 -1
- package/dist/types/sdk/bus/slack-daemon.d.ts +1 -1
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +1 -1
- package/dist/types/sdk/cli/index.d.ts +1 -0
- package/dist/types/sdk/cli/master-cli.d.ts +63 -0
- package/dist/types/sdk/cli/rows.d.ts +12 -5
- package/dist/types/sdk/cli/session-cli.d.ts +20 -3
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/host/control/dispatch.d.ts +10 -0
- package/dist/types/sdk/host/control/index.d.ts +1 -1
- package/dist/types/sdk/host/control/operations.d.ts +2 -0
- package/dist/types/sdk/host/control/runtime-gate.d.ts +21 -7
- package/dist/types/sdk/host/events.d.ts +1 -0
- package/dist/types/sdk/host/host.d.ts +10 -0
- package/dist/types/sdk/host/query/cursor.d.ts +4 -2
- package/dist/types/sdk/host/query/handlers.d.ts +1 -0
- package/dist/types/sdk/host/query/revision-store.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +70 -17
- package/dist/types/sdk/lifecycle/broker-client.d.ts +6 -0
- package/dist/types/sdk/lifecycle/service.d.ts +15 -3
- package/dist/types/sdk/lifecycle-session.d.ts +3 -1
- package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -5
- package/dist/types/sdk/receipt-state.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +6 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/sdk/turn-result.d.ts +6 -0
- package/dist/types/session/agent-session.d.ts +52 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +29 -2
- package/dist/types/session/session-manager.d.ts +79 -2
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/bash.d.ts +16 -0
- package/dist/types/tools/browser/launch.d.ts +10 -0
- package/dist/types/tools/browser/registry.d.ts +8 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -10
- package/dist/types/tools/job.d.ts +3 -1
- package/dist/types/utils/external-editor.d.ts +7 -0
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/pirate.ts +7 -4
- package/package.json +7 -7
- package/scripts/generate-sdk-operation-inventory.ts +13 -1
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +107 -9
- package/src/browser-backend.ts +29 -0
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/fast-help.ts +1 -0
- package/src/cli/plugin-cli.ts +29 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +140 -23
- package/src/commands/launch.ts +86 -18
- package/src/commands/sdk.ts +135 -28
- package/src/config/autorouting-tier-map.ts +12 -0
- package/src/config/billing-path.ts +42 -0
- package/src/config/config-schema-version.ts +18 -1
- package/src/config/file-lock.ts +158 -83
- package/src/config/model-preset-registry.ts +178 -23
- package/src/config/model-profiles.ts +5 -5
- package/src/config/model-registry.ts +291 -42
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +27 -8
- package/src/config/settings.ts +51 -15
- package/src/coordinator-mcp/projection-scan.ts +27 -6
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +115 -21
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +1 -1
- package/src/extensibility/extensions/runner.ts +27 -0
- package/src/extensibility/extensions/types.ts +8 -3
- package/src/extensibility/gjc-plugins/installer.ts +51 -11
- package/src/extensibility/runtime-skill-discovery.ts +110 -3
- package/src/extensibility/skills.ts +22 -12
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/launch-worktree.ts +285 -4
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +213 -14
- package/src/gjc-runtime/session-state-sidecar.ts +938 -39
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +18 -3
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/internal-urls/docs-index.generated.ts +15 -15
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/main.ts +61 -3
- package/src/master-mode/context.ts +50 -0
- package/src/master-mode/first-request.ts +65 -0
- package/src/master-mode/snapshot.ts +205 -0
- package/src/modes/acp/acp-agent.ts +610 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/model-selector.ts +76 -8
- package/src/modes/components/oauth-selector.ts +6 -1
- package/src/modes/components/session-selector.ts +23 -15
- package/src/modes/components/sessions-dashboard.ts +8 -5
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/model-name.ts +53 -0
- package/src/modes/components/status-line/priority-row.ts +107 -0
- package/src/modes/components/status-line/segments.ts +23 -9
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +30 -4
- package/src/modes/controllers/extension-ui-controller.ts +13 -13
- package/src/modes/controllers/input-controller.ts +30 -2
- package/src/modes/controllers/selector-controller.ts +200 -98
- package/src/modes/interactive-mode.ts +36 -12
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/prompt-action-autocomplete.ts +30 -4
- package/src/modes/runtime-init.ts +12 -9
- package/src/modes/theme/theme.ts +5 -0
- package/src/modes/types.ts +2 -0
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/ui-helpers.ts +22 -3
- package/src/prompts/agent-fragments/browser-aside.md +22 -0
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/ask.md +1 -1
- package/src/prompts/tools/bash.md +1 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +5 -0
- package/src/sdk/broker/broker.ts +1646 -27
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +81 -3
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle-ledger.ts +6 -0
- package/src/sdk/broker/lifecycle.ts +440 -75
- package/src/sdk/broker/master-capability.ts +232 -0
- package/src/sdk/broker/session-index.ts +121 -17
- package/src/sdk/broker/session-scope.ts +272 -0
- package/src/sdk/broker/spawn-authority.ts +1031 -0
- package/src/sdk/broker/spawn-substrate.ts +591 -0
- package/src/sdk/broker/startup-budget.ts +144 -2
- package/src/sdk/broker/state-version.ts +43 -14
- package/src/sdk/broker/transport.ts +1 -0
- package/src/sdk/broker/worktree-occupancy.ts +90 -0
- package/src/sdk/bus/chat-daemon-control.ts +5 -2
- package/src/sdk/bus/chat-daemon-runtime.ts +136 -18
- package/src/sdk/bus/discord-daemon.ts +50 -10
- package/src/sdk/bus/index.ts +84 -7
- package/src/sdk/bus/kind-aware-reconciliation.ts +296 -48
- package/src/sdk/bus/lifecycle-commands.ts +4 -0
- package/src/sdk/bus/notification-service.ts +22 -5
- package/src/sdk/bus/prompt-reconciliation.ts +7 -2
- package/src/sdk/bus/reconciliation-store.ts +55 -31
- package/src/sdk/bus/slack-conversation.ts +1 -1
- package/src/sdk/bus/slack-daemon.ts +72 -38
- package/src/sdk/bus/telegram-daemon-contract.ts +1 -1
- package/src/sdk/cli/index.ts +1 -0
- package/src/sdk/cli/master-cli.ts +259 -0
- package/src/sdk/cli/rows.ts +41 -15
- package/src/sdk/cli/session-cli.ts +297 -26
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/host/control/dispatch.ts +101 -40
- package/src/sdk/host/control/index.ts +2 -0
- package/src/sdk/host/control/operations.ts +2 -0
- package/src/sdk/host/control/runtime-gate.ts +203 -11
- package/src/sdk/host/events.ts +1 -0
- package/src/sdk/host/host.ts +29 -2
- package/src/sdk/host/query/cursor.ts +52 -7
- package/src/sdk/host/query/handlers.ts +33 -12
- package/src/sdk/host/query/revision-store.ts +12 -0
- package/src/sdk/host/session-runtime.test.ts +673 -14
- package/src/sdk/host/session-runtime.ts +674 -103
- package/src/sdk/host/surface-policy.ts +3 -3
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +17 -4
- package/src/sdk/prompt-deadline-manager.ts +2 -1
- package/src/sdk/protocol/operation-inventory.generated.json +105 -3
- package/src/sdk/protocol/operation-registry.ts +14 -4
- package/src/sdk/receipt-state.ts +10 -0
- package/src/sdk/router/session-router.ts +219 -118
- package/src/sdk/session.ts +416 -195
- package/src/sdk/startup-capability.ts +1 -0
- package/src/sdk/turn-result.ts +22 -0
- package/src/session/agent-session.ts +1628 -318
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +40 -3
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/session-manager.ts +850 -97
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/slash-commands/builtin-registry.ts +41 -0
- package/src/task/executor.ts +9 -4
- package/src/telemetry/events.ts +1138 -12
- package/src/tools/ask-contract.test.ts +64 -0
- package/src/tools/ask-contract.ts +36 -1
- package/src/tools/ask.ts +10 -8
- package/src/tools/bash-interactive.ts +11 -2
- package/src/tools/bash.ts +412 -75
- package/src/tools/browser/launch.ts +37 -0
- package/src/tools/browser/registry.ts +99 -9
- package/src/tools/cron.ts +6 -4
- package/src/tools/descriptors.test.ts +15 -0
- package/src/tools/descriptors.ts +5 -1
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +10 -6
- package/src/tools/job.ts +4 -1
- package/src/tools/monitor.ts +12 -9
- package/src/tools/resolve.ts +30 -10
- package/src/tools/tool-catalog.generated.ts +2 -2
- package/src/utils/external-editor.ts +72 -1
|
@@ -375,8 +375,10 @@ function buildSingleResource({
|
|
|
375
375
|
|
|
376
376
|
function formatFileLine(idx: number, file: PrDiffFile, repo: string, prNumber: number): string {
|
|
377
377
|
const stats = file.changeType === "binary" ? "(binary)" : `+${file.additions} -${file.deletions}`;
|
|
378
|
-
const
|
|
379
|
-
|
|
378
|
+
const escapedNote = file.pathEscaped ? " (escaped non-UTF-8 name)" : "";
|
|
379
|
+
const renameNote = file.oldPathEscaped ? " (escaped non-UTF-8 name)" : "";
|
|
380
|
+
const rename = file.oldPath ? ` (renamed from ${file.oldPath}${renameNote})` : "";
|
|
381
|
+
return `${idx}. ${file.path}${escapedNote} ${stats} [${file.changeType}]${rename}\n pr://${repo}/${prNumber}/diff/${idx}`;
|
|
380
382
|
}
|
|
381
383
|
|
|
382
384
|
async function fetchAndRenderPrDiff(
|
package/src/main.ts
CHANGED
|
@@ -39,7 +39,9 @@ import { BUNDLED_GROK_BUILD_EXTENSION_ID, getBundledGrokBuildExtensionFactory }
|
|
|
39
39
|
import { initializeWithSettings } from "./discovery";
|
|
40
40
|
import { exportFromFile } from "./export/html";
|
|
41
41
|
import type { ExtensionUIContext } from "./extensibility/extensions/types";
|
|
42
|
+
import { releaseLaunchWorktreeReservationAfterRegistration } from "./gjc-runtime/launch-worktree-reservation";
|
|
42
43
|
import { persistCoordinatorRuntimeInputReady } from "./gjc-runtime/session-state-sidecar";
|
|
44
|
+
import { assertMasterLaunchArgs, assertMasterLaunchDisposition, createMasterModeContext } from "./master-mode/context";
|
|
43
45
|
import type { AcpStartupOptions } from "./modes/acp/startup-options";
|
|
44
46
|
import type { SessionSelectionResult } from "./modes/components/session-selector";
|
|
45
47
|
import type { InteractiveMode } from "./modes/interactive-mode";
|
|
@@ -56,7 +58,7 @@ import {
|
|
|
56
58
|
discoverAuthStorage,
|
|
57
59
|
} from "./sdk";
|
|
58
60
|
import { processIncarnation } from "./sdk/broker/process-incarnation";
|
|
59
|
-
import { SessionIndex } from "./sdk/broker/session-index";
|
|
61
|
+
import { newMasterAttestationEpoch, resolveSessionLocator, SessionIndex } from "./sdk/broker/session-index";
|
|
60
62
|
import type { AgentSession } from "./session/agent-session";
|
|
61
63
|
import { SessionMigrationBusyError } from "./session/internal/session-open-errors";
|
|
62
64
|
import {
|
|
@@ -451,6 +453,7 @@ type StartupModelProfileArgs = {
|
|
|
451
453
|
startupModel?: CreateAgentSessionOptions["model"];
|
|
452
454
|
startupThinkingLevel?: CreateAgentSessionOptions["thinkingLevel"];
|
|
453
455
|
preferCachedModels?: boolean;
|
|
456
|
+
preferCachedDefaultProfile?: boolean;
|
|
454
457
|
};
|
|
455
458
|
|
|
456
459
|
async function applyStartupModelProfilesWithPolicy(
|
|
@@ -482,7 +485,9 @@ async function applyStartupModelProfilesWithPolicy(
|
|
|
482
485
|
// deferred `--model <pattern>` path resolved inside createAgentSession.
|
|
483
486
|
const explicitModel = args.parsedArgs.model ? (args.startupModel ?? args.session.model) : undefined;
|
|
484
487
|
const defaultProfile = args.settings.get("modelProfile.default");
|
|
485
|
-
const preferCachedProfiles =
|
|
488
|
+
const preferCachedProfiles =
|
|
489
|
+
(args.preferCachedModels === true && args.parsedArgs.mpreset !== undefined) ||
|
|
490
|
+
(args.preferCachedDefaultProfile === true && defaultProfile !== undefined);
|
|
486
491
|
const applyConfiguredProfiles = async (): Promise<boolean> => {
|
|
487
492
|
let applied = true;
|
|
488
493
|
if (defaultProfile) {
|
|
@@ -752,6 +757,9 @@ export async function runInteractiveMode(
|
|
|
752
757
|
if (
|
|
753
758
|
shouldOfferAutomaticOnboarding({
|
|
754
759
|
normalInteractive: true,
|
|
760
|
+
automation:
|
|
761
|
+
/^(?:1|true|yes|on)$/i.test(process.env.CI ?? "") ||
|
|
762
|
+
/^(?:1|true|yes|on)$/i.test(process.env.GJC_AUTOMATION ?? ""),
|
|
755
763
|
initialMessage,
|
|
756
764
|
initialMessages,
|
|
757
765
|
initialImages,
|
|
@@ -1366,6 +1374,7 @@ export async function runRootCommand(
|
|
|
1366
1374
|
deps: RunRootCommandDependencies = {},
|
|
1367
1375
|
): Promise<void> {
|
|
1368
1376
|
const parsedArgs = parsed;
|
|
1377
|
+
assertMasterLaunchArgs(parsedArgs);
|
|
1369
1378
|
let initialThemeInitialized = false;
|
|
1370
1379
|
let autoChdirApplied = false;
|
|
1371
1380
|
let bareResumeSessionManager: SessionManager | undefined;
|
|
@@ -1553,6 +1562,22 @@ export async function runRootCommand(
|
|
|
1553
1562
|
print: Boolean(parsedArgs.print),
|
|
1554
1563
|
mode: parsedArgs.mode,
|
|
1555
1564
|
});
|
|
1565
|
+
// Master admission runs unconditionally and BEFORE any session state is
|
|
1566
|
+
// opened or created. Nesting it under nonInteractiveError skipped it for a
|
|
1567
|
+
// non-TTY launch carrying prepared input, which resolves to autoPrint with no
|
|
1568
|
+
// error, so `--master <prompt>` proceeded as an ordinary auto-print run.
|
|
1569
|
+
try {
|
|
1570
|
+
assertMasterLaunchDisposition({
|
|
1571
|
+
master: parsedArgs.master,
|
|
1572
|
+
isInteractive: disposition.isInteractive,
|
|
1573
|
+
autoPrint: disposition.autoPrint,
|
|
1574
|
+
nonInteractiveError: disposition.nonInteractiveError,
|
|
1575
|
+
});
|
|
1576
|
+
} catch (error) {
|
|
1577
|
+
process.stderr.write(`${chalk.red(error instanceof Error ? error.message : "Invalid master launch")}\n`);
|
|
1578
|
+
if (!deps.suppressProcessExit) process.exitCode = 1;
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1556
1581
|
if (disposition.nonInteractiveError) {
|
|
1557
1582
|
process.stderr.write(`${chalk.red(disposition.nonInteractiveError)}\n`);
|
|
1558
1583
|
process.exit(1);
|
|
@@ -1676,6 +1701,14 @@ export async function runRootCommand(
|
|
|
1676
1701
|
}
|
|
1677
1702
|
}
|
|
1678
1703
|
}
|
|
1704
|
+
const masterModeContext =
|
|
1705
|
+
parsedArgs.master === true && sessionManager
|
|
1706
|
+
? createMasterModeContext(
|
|
1707
|
+
parsedArgs.masterScope ?? "repo",
|
|
1708
|
+
sessionManager.getSessionId(),
|
|
1709
|
+
newMasterAttestationEpoch(),
|
|
1710
|
+
)
|
|
1711
|
+
: undefined;
|
|
1679
1712
|
|
|
1680
1713
|
const { options: sessionOptions } = await logger.time(
|
|
1681
1714
|
"buildSessionOptions",
|
|
@@ -1742,6 +1775,7 @@ export async function runRootCommand(
|
|
|
1742
1775
|
sessionOptions.notificationHostModeSupported = isInteractive;
|
|
1743
1776
|
sessionOptions.sdkHostModeSupported = isInteractive;
|
|
1744
1777
|
sessionOptions.settings = settingsInstance;
|
|
1778
|
+
sessionOptions.masterModeContext = masterModeContext;
|
|
1745
1779
|
if (isInteractive && sessionOptions.mcpConfigPath) {
|
|
1746
1780
|
sessionOptions.deferMcpConfigStartup = true;
|
|
1747
1781
|
}
|
|
@@ -1826,7 +1860,7 @@ export async function runRootCommand(
|
|
|
1826
1860
|
const directSessionId = process.env.GJC_LIFECYCLE_REQUEST_ID ? undefined : sessionManager?.getSessionId();
|
|
1827
1861
|
if (directSessionId) {
|
|
1828
1862
|
const sessionIndex = new SessionIndex(settingsInstance.getAgentDir());
|
|
1829
|
-
const locator =
|
|
1863
|
+
const locator = await resolveSessionLocator(sessionManager?.getCwd() ?? cwd, settingsInstance.getAgentDir());
|
|
1830
1864
|
// A pid is reusable, so the broker's teardown fence needs this session's OS
|
|
1831
1865
|
// start incarnation recorded alongside the pid it publishes.
|
|
1832
1866
|
const directSessionIncarnation = processIncarnation(process.pid);
|
|
@@ -1837,7 +1871,31 @@ export async function runRootCommand(
|
|
|
1837
1871
|
endpointGeneration: 0,
|
|
1838
1872
|
pid: process.pid,
|
|
1839
1873
|
...(directSessionIncarnation ? { processIncarnation: directSessionIncarnation } : {}),
|
|
1874
|
+
...(masterModeContext && directSessionIncarnation
|
|
1875
|
+
? {
|
|
1876
|
+
masterRole: {
|
|
1877
|
+
version: 2,
|
|
1878
|
+
ownerSessionId: masterModeContext.ownerSessionId,
|
|
1879
|
+
launchPid: process.pid,
|
|
1880
|
+
launchProcessIncarnation: directSessionIncarnation,
|
|
1881
|
+
role: "master" as const,
|
|
1882
|
+
attestationEpoch: masterModeContext.attestationEpoch,
|
|
1883
|
+
},
|
|
1884
|
+
}
|
|
1885
|
+
: {}),
|
|
1840
1886
|
});
|
|
1887
|
+
if (locator.worktreeRoot !== null) {
|
|
1888
|
+
try {
|
|
1889
|
+
await releaseLaunchWorktreeReservationAfterRegistration(
|
|
1890
|
+
settingsInstance.getAgentDir(),
|
|
1891
|
+
locator.worktreeRoot,
|
|
1892
|
+
);
|
|
1893
|
+
} catch (error) {
|
|
1894
|
+
logger.warn("Failed to release launch worktree reservation after host registration", {
|
|
1895
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1841
1899
|
postmortem.register("direct-session-index", async () => {
|
|
1842
1900
|
await sessionIndex.append({
|
|
1843
1901
|
type: "host_unregistered",
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import type { Args } from "../cli/args";
|
|
3
|
+
|
|
4
|
+
export type MasterScope = "repo" | "pwd" | "global";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Process-local master authority. Its capability accessor is deliberately the
|
|
8
|
+
* only way to reach the raw capability; this context is never serializable.
|
|
9
|
+
*/
|
|
10
|
+
export interface MasterModeContext {
|
|
11
|
+
readonly scope: MasterScope;
|
|
12
|
+
readonly ownerSessionId: string;
|
|
13
|
+
readonly attestationEpoch: string;
|
|
14
|
+
getCapability(): string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function createMasterModeContext(
|
|
18
|
+
scope: MasterScope,
|
|
19
|
+
ownerSessionId: string,
|
|
20
|
+
attestationEpoch: string,
|
|
21
|
+
): MasterModeContext {
|
|
22
|
+
const capability = randomBytes(32).toString("base64url");
|
|
23
|
+
return { scope, ownerSessionId, attestationEpoch, getCapability: () => capability };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Reject routes that cannot establish an interactive, durable master session. */
|
|
27
|
+
export function assertMasterLaunchArgs(parsed: Args): void {
|
|
28
|
+
if (parsed.master !== true) return;
|
|
29
|
+
if (
|
|
30
|
+
parsed.print === true ||
|
|
31
|
+
parsed.mode !== undefined ||
|
|
32
|
+
parsed.export !== undefined ||
|
|
33
|
+
parsed.listModels !== undefined ||
|
|
34
|
+
parsed.noSession === true
|
|
35
|
+
) {
|
|
36
|
+
throw new Error("--master requires an interactive durable TUI launch");
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function assertMasterLaunchDisposition(input: {
|
|
41
|
+
master: boolean | undefined;
|
|
42
|
+
isInteractive: boolean;
|
|
43
|
+
autoPrint: boolean;
|
|
44
|
+
nonInteractiveError?: string;
|
|
45
|
+
}): void {
|
|
46
|
+
if (input.master !== true) return;
|
|
47
|
+
if (!input.isInteractive || input.autoPrint || input.nonInteractiveError !== undefined) {
|
|
48
|
+
throw new Error("--master requires an interactive TTY launch");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { canonicalSessionCwd, sessionWorktreeRoot } from "../sdk/broker/session-index";
|
|
2
|
+
import type { MasterScope } from "./context";
|
|
3
|
+
import { collectMasterPeerSnapshot, type MasterPeerSnapshotLifecycle, renderMasterPeerSnapshot } from "./snapshot";
|
|
4
|
+
|
|
5
|
+
export const MASTER_PEER_SNAPSHOT_CUSTOM_TYPE = "master-peer-snapshot";
|
|
6
|
+
|
|
7
|
+
/** The injected message shape consumed by the agent-session contributor seam. */
|
|
8
|
+
export interface MasterPeerSnapshotMessage {
|
|
9
|
+
customType: string;
|
|
10
|
+
content: string;
|
|
11
|
+
display: false;
|
|
12
|
+
attribution: "agent";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MasterPeerSnapshotContributorInput {
|
|
16
|
+
readonly lifecycle: MasterPeerSnapshotLifecycle;
|
|
17
|
+
readonly ownerSessionId: string;
|
|
18
|
+
/** Current transcript/session identity; differs from ownerSessionId after a branch or switch. */
|
|
19
|
+
readonly getSessionId?: () => string;
|
|
20
|
+
readonly scope: MasterScope;
|
|
21
|
+
/** Live cwd accessor; resolved at injection time, not at registration. */
|
|
22
|
+
readonly getCwd: () => string;
|
|
23
|
+
/**
|
|
24
|
+
* Whether a prior injection was persisted to the session branch. A persisted
|
|
25
|
+
* block proves a first request was accepted, so later turns never duplicate
|
|
26
|
+
* it; a pre-accept cancellation persists nothing and the next attempt
|
|
27
|
+
* re-collects a fresh snapshot.
|
|
28
|
+
*/
|
|
29
|
+
readonly hasPersistedInjection: () => boolean;
|
|
30
|
+
readonly onError?: (error: unknown) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Builds the one-shot before-agent-start contributor that injects the scoped,
|
|
35
|
+
* no-probe master peer snapshot immediately before the first accepted provider
|
|
36
|
+
* request. Idle masters never collect: the snapshot is gathered only when a
|
|
37
|
+
* prompt actually starts.
|
|
38
|
+
*/
|
|
39
|
+
export function createMasterPeerSnapshotContributor(
|
|
40
|
+
input: MasterPeerSnapshotContributorInput,
|
|
41
|
+
): () => Promise<MasterPeerSnapshotMessage | undefined> {
|
|
42
|
+
return async () => {
|
|
43
|
+
try {
|
|
44
|
+
if (input.hasPersistedInjection()) return undefined;
|
|
45
|
+
const anchorCwd = await canonicalSessionCwd(input.getCwd());
|
|
46
|
+
const snapshot = await collectMasterPeerSnapshot({
|
|
47
|
+
lifecycle: input.lifecycle,
|
|
48
|
+
actor: { id: input.ownerSessionId, namespace: "sdk:master-snapshot" },
|
|
49
|
+
ownerSessionId: input.ownerSessionId,
|
|
50
|
+
...(input.getSessionId === undefined ? {} : { currentSessionId: input.getSessionId() }),
|
|
51
|
+
scope: input.scope,
|
|
52
|
+
requestAnchor: { cwd: anchorCwd, worktreeRoot: await sessionWorktreeRoot(anchorCwd) },
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
customType: MASTER_PEER_SNAPSHOT_CUSTOM_TYPE,
|
|
56
|
+
content: renderMasterPeerSnapshot(snapshot),
|
|
57
|
+
display: false,
|
|
58
|
+
attribution: "agent",
|
|
59
|
+
};
|
|
60
|
+
} catch (error) {
|
|
61
|
+
input.onError?.(error);
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ResolvedScopeV1,
|
|
3
|
+
resolveScopeRequest,
|
|
4
|
+
type ScopeNameV1,
|
|
5
|
+
type ScopeRequestV1,
|
|
6
|
+
type SdkSearchResultV1,
|
|
7
|
+
type SdkSearchRowV1,
|
|
8
|
+
sdkSearchResultV1,
|
|
9
|
+
} from "../sdk/broker/session-scope";
|
|
10
|
+
import type { SessionLifecycleActor, SessionListOutcome, SessionListRequest } from "../sdk/lifecycle/service";
|
|
11
|
+
|
|
12
|
+
export type MasterPeerSnapshot = {
|
|
13
|
+
readonly status: SdkSearchResultV1["status"];
|
|
14
|
+
readonly scope: ResolvedScopeV1;
|
|
15
|
+
readonly observedAt: string;
|
|
16
|
+
readonly indexSeq?: number;
|
|
17
|
+
readonly rows: readonly SdkSearchRowV1[];
|
|
18
|
+
readonly truncated?: boolean;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const MASTER_PEER_SNAPSHOT_MAX_ROWS = 256;
|
|
22
|
+
const MASTER_PEER_SNAPSHOT_MAX_BYTES = 64 * 1024;
|
|
23
|
+
|
|
24
|
+
/** The scoped Broker surface needed for the one-time master peer snapshot. */
|
|
25
|
+
export interface MasterPeerSnapshotLifecycle {
|
|
26
|
+
list(request: Omit<SessionListRequest, "operation">): Promise<SessionListOutcome>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface CollectMasterPeerSnapshotInput {
|
|
30
|
+
readonly lifecycle: MasterPeerSnapshotLifecycle;
|
|
31
|
+
readonly actor: SessionLifecycleActor;
|
|
32
|
+
readonly ownerSessionId: string;
|
|
33
|
+
readonly currentSessionId?: string;
|
|
34
|
+
readonly scope: ScopeNameV1;
|
|
35
|
+
readonly requestAnchor: ScopeRequestV1["requestAnchor"];
|
|
36
|
+
readonly timeoutMs?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function compareText(left: string, right: string): number {
|
|
40
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Orders by session ID, then the complete canonical locator as a deterministic
|
|
45
|
+
* tie-breaker. The Broker's `live` value is index evidence, not a fresh probe.
|
|
46
|
+
*/
|
|
47
|
+
function compareRows(left: SdkSearchRowV1, right: SdkSearchRowV1): number {
|
|
48
|
+
const id = compareText(left.id, right.id);
|
|
49
|
+
if (id !== 0) return id;
|
|
50
|
+
const cwd = compareText(left.locator.cwd, right.locator.cwd);
|
|
51
|
+
if (cwd !== 0) return cwd;
|
|
52
|
+
const worktreeRoot = compareText(left.locator.worktreeRoot ?? "", right.locator.worktreeRoot ?? "");
|
|
53
|
+
if (worktreeRoot !== 0) return worktreeRoot;
|
|
54
|
+
return compareText(left.locator.stateRoot, right.locator.stateRoot);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function unavailableSnapshot(scope: ResolvedScopeV1): MasterPeerSnapshot {
|
|
58
|
+
return {
|
|
59
|
+
status: "unavailable",
|
|
60
|
+
scope,
|
|
61
|
+
observedAt: new Date().toISOString(),
|
|
62
|
+
rows: [],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function matchesResolvedScope(left: ResolvedScopeV1, right: ResolvedScopeV1): boolean {
|
|
67
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Retains a row prefix only when its final escaped prompt block fits the budget. */
|
|
71
|
+
function boundSnapshotRows(
|
|
72
|
+
snapshot: Omit<MasterPeerSnapshot, "rows" | "truncated">,
|
|
73
|
+
rows: readonly SdkSearchRowV1[],
|
|
74
|
+
): {
|
|
75
|
+
rows: readonly SdkSearchRowV1[];
|
|
76
|
+
truncated: boolean;
|
|
77
|
+
} {
|
|
78
|
+
const retained: SdkSearchRowV1[] = [];
|
|
79
|
+
for (const [index, row] of rows.entries()) {
|
|
80
|
+
if (retained.length >= MASTER_PEER_SNAPSHOT_MAX_ROWS) return { rows: retained, truncated: true };
|
|
81
|
+
|
|
82
|
+
retained.push(row);
|
|
83
|
+
const willTruncate = index < rows.length - 1;
|
|
84
|
+
const candidate: MasterPeerSnapshot = {
|
|
85
|
+
...snapshot,
|
|
86
|
+
rows: retained,
|
|
87
|
+
...(willTruncate ? { truncated: true } : {}),
|
|
88
|
+
};
|
|
89
|
+
if (Buffer.byteLength(renderMasterPeerSnapshot(candidate), "utf8") > MASTER_PEER_SNAPSHOT_MAX_BYTES) {
|
|
90
|
+
retained.pop();
|
|
91
|
+
return { rows: retained, truncated: true };
|
|
92
|
+
}
|
|
93
|
+
if (willTruncate && retained.length === MASTER_PEER_SNAPSHOT_MAX_ROWS) return { rows: retained, truncated: true };
|
|
94
|
+
}
|
|
95
|
+
return { rows: retained, truncated: false };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Collects the first-request peer snapshot through scoped Broker session.list.
|
|
100
|
+
* It deliberately has no Router or endpoint-probe dependency.
|
|
101
|
+
*/
|
|
102
|
+
export async function collectMasterPeerSnapshot(input: CollectMasterPeerSnapshotInput): Promise<MasterPeerSnapshot> {
|
|
103
|
+
const request: ScopeRequestV1 = {
|
|
104
|
+
version: 1,
|
|
105
|
+
requested: input.scope,
|
|
106
|
+
requestAnchor: input.requestAnchor,
|
|
107
|
+
};
|
|
108
|
+
const resolvedScope = await resolveScopeRequest(request);
|
|
109
|
+
const outcome = await input.lifecycle.list({
|
|
110
|
+
actor: input.actor,
|
|
111
|
+
capability: "session.list",
|
|
112
|
+
target: { scope: request },
|
|
113
|
+
...(input.timeoutMs === undefined ? {} : { timeoutMs: input.timeoutMs }),
|
|
114
|
+
});
|
|
115
|
+
const result = sdkSearchResultV1(outcome.result);
|
|
116
|
+
if (!result || !matchesResolvedScope(result.scope, resolvedScope)) return unavailableSnapshot(resolvedScope);
|
|
117
|
+
const snapshot: Omit<MasterPeerSnapshot, "rows" | "truncated"> = {
|
|
118
|
+
status: result.status,
|
|
119
|
+
scope: result.scope,
|
|
120
|
+
observedAt: result.observedAt,
|
|
121
|
+
...(result.indexSeq === undefined ? {} : { indexSeq: result.indexSeq }),
|
|
122
|
+
};
|
|
123
|
+
const rows = result.rows
|
|
124
|
+
.filter(row => row.id !== (input.currentSessionId ?? input.ownerSessionId))
|
|
125
|
+
.sort(compareRows);
|
|
126
|
+
const bounded = boundSnapshotRows(snapshot, rows);
|
|
127
|
+
return {
|
|
128
|
+
...snapshot,
|
|
129
|
+
rows: bounded.rows,
|
|
130
|
+
...(bounded.truncated ? { truncated: true } : {}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Escapes untrusted metadata before it is placed in fixed prompt framing. */
|
|
135
|
+
export function escapeMasterPeerSnapshotText(input: string): string {
|
|
136
|
+
let firstEscapable = -1;
|
|
137
|
+
for (let index = 0; index < input.length; index++) {
|
|
138
|
+
const code = input.charCodeAt(index);
|
|
139
|
+
if (code === 38 || code === 60 || code === 62 || code === 96) {
|
|
140
|
+
firstEscapable = index;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (firstEscapable === -1) return input;
|
|
145
|
+
|
|
146
|
+
let output = input.slice(0, firstEscapable);
|
|
147
|
+
for (let index = firstEscapable; index < input.length; index++) {
|
|
148
|
+
const character = input[index];
|
|
149
|
+
if (character === "&") output += "&";
|
|
150
|
+
else if (character === "<") output += "<";
|
|
151
|
+
else if (character === ">") output += ">";
|
|
152
|
+
else if (character === "`") output += "`";
|
|
153
|
+
else output += character;
|
|
154
|
+
}
|
|
155
|
+
return output;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function escapeOptionalText(input: string | null): string | null {
|
|
159
|
+
return input === null ? null : escapeMasterPeerSnapshotText(input);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderedScope(scope: ResolvedScopeV1): ResolvedScopeV1 {
|
|
163
|
+
const resolved =
|
|
164
|
+
scope.resolved === null
|
|
165
|
+
? null
|
|
166
|
+
: scope.resolved.kind === "repo"
|
|
167
|
+
? { kind: "repo" as const, worktreeRoot: escapeMasterPeerSnapshotText(scope.resolved.worktreeRoot) }
|
|
168
|
+
: scope.resolved.kind === "pwd"
|
|
169
|
+
? { kind: "pwd" as const, cwd: escapeMasterPeerSnapshotText(scope.resolved.cwd) }
|
|
170
|
+
: { kind: "global" as const, visibility: scope.resolved.visibility };
|
|
171
|
+
return {
|
|
172
|
+
version: scope.version,
|
|
173
|
+
requested: escapeMasterPeerSnapshotText(scope.requested) as ScopeNameV1,
|
|
174
|
+
requestAnchor: {
|
|
175
|
+
cwd: escapeMasterPeerSnapshotText(scope.requestAnchor.cwd),
|
|
176
|
+
worktreeRoot: escapeOptionalText(scope.requestAnchor.worktreeRoot),
|
|
177
|
+
},
|
|
178
|
+
resolved,
|
|
179
|
+
resolution: escapeMasterPeerSnapshotText(scope.resolution) as ResolvedScopeV1["resolution"],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function renderedRows(rows: readonly SdkSearchRowV1[]): readonly SdkSearchRowV1[] {
|
|
184
|
+
return rows.map(row => ({
|
|
185
|
+
id: escapeMasterPeerSnapshotText(row.id),
|
|
186
|
+
locator: {
|
|
187
|
+
cwd: escapeMasterPeerSnapshotText(row.locator.cwd),
|
|
188
|
+
worktreeRoot: escapeOptionalText(row.locator.worktreeRoot),
|
|
189
|
+
stateRoot: escapeMasterPeerSnapshotText(row.locator.stateRoot),
|
|
190
|
+
},
|
|
191
|
+
live: row.live,
|
|
192
|
+
}));
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Renders the fixed, escaped prompt block for one master peer observation. */
|
|
196
|
+
export function renderMasterPeerSnapshot(snapshot: MasterPeerSnapshot): string {
|
|
197
|
+
const content = {
|
|
198
|
+
scope: renderedScope(snapshot.scope),
|
|
199
|
+
observedAt: escapeMasterPeerSnapshotText(snapshot.observedAt),
|
|
200
|
+
...(snapshot.indexSeq === undefined ? {} : { indexSeq: snapshot.indexSeq }),
|
|
201
|
+
...(snapshot.truncated === undefined ? {} : { truncated: snapshot.truncated }),
|
|
202
|
+
rows: renderedRows(snapshot.rows),
|
|
203
|
+
};
|
|
204
|
+
return `<gjc-master-peer-snapshot>\nstatus: ${escapeMasterPeerSnapshotText(snapshot.status)}\n${JSON.stringify(content)}\n</gjc-master-peer-snapshot>`;
|
|
205
|
+
}
|