@gajae-code/coding-agent 0.15.5 → 0.16.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 +85 -1
- package/dist/types/async/job-manager.d.ts +83 -0
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +17 -0
- package/dist/types/cli/model-presets-cli.d.ts +9 -0
- package/dist/types/cli/root-flags.d.ts +14 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/cli.d.ts +7 -0
- package/dist/types/commands/auth-gateway.d.ts +3 -0
- package/dist/types/commands/gc.d.ts +13 -0
- package/dist/types/commands/launch.d.ts +10 -0
- package/dist/types/commands/model-presets.d.ts +21 -0
- package/dist/types/config/autorouting-tier-map.d.ts +120 -0
- package/dist/types/config/file-lock.d.ts +54 -2
- package/dist/types/config/internal/model-preset-registry-test-state.d.ts +4 -0
- package/dist/types/config/internal/model-preset-registry-test-support.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +3 -2
- package/dist/types/config/model-preset-registry.d.ts +368 -0
- package/dist/types/config/model-profile-activation.d.ts +39 -0
- package/dist/types/config/model-profiles.d.ts +5 -3
- package/dist/types/config/model-registry.d.ts +17 -5
- package/dist/types/config/settings-schema.d.ts +19 -1
- package/dist/types/config/settings.d.ts +2 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +7 -5
- package/dist/types/coordinator-mcp/projection-scan.d.ts +54 -0
- package/dist/types/crash/record-loader.d.ts +15 -0
- package/dist/types/extensibility/extensions/types.d.ts +2 -5
- package/dist/types/gjc-runtime/empty-delete-gc.d.ts +58 -0
- package/dist/types/gjc-runtime/gc-runtime.d.ts +2 -0
- package/dist/types/gjc-runtime/launch-worktree-reservation.d.ts +21 -0
- package/dist/types/gjc-runtime/managed-owner-supervisor.d.ts +2 -0
- package/dist/types/gjc-runtime/session-state-lock.d.ts +35 -6
- package/dist/types/gjc-runtime/session-state-sidecar.d.ts +72 -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/index.d.ts +1 -0
- package/dist/types/main.d.ts +3 -1
- 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/components/frictionless-onboarding-selector.d.ts +10 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +4 -2
- package/dist/types/modes/components/oauth-selector.d.ts +1 -0
- 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/jobs-observer.d.ts +36 -0
- package/dist/types/modes/print-mode.d.ts +17 -0
- package/dist/types/modes/ui-language.d.ts +7 -1
- package/dist/types/sdk/broker/broker.d.ts +80 -1
- package/dist/types/sdk/broker/endpoint-authority.d.ts +24 -0
- package/dist/types/sdk/broker/index.d.ts +3 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +26 -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/state-version.d.ts +4 -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/index.d.ts +6 -0
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +3 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +4 -0
- 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 +4 -3
- package/dist/types/sdk/cli/session-cli.d.ts +18 -2
- package/dist/types/sdk/client/client.d.ts +3 -2
- package/dist/types/sdk/client/discovery.d.ts +1 -0
- 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/host.d.ts +10 -0
- package/dist/types/sdk/host/model-pin.d.ts +7 -2
- package/dist/types/sdk/host/sdk-run-capability.d.ts +1 -0
- package/dist/types/sdk/host/session-runtime.d.ts +67 -1
- 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 +2 -0
- package/dist/types/sdk/prompt-deadline-manager.d.ts +11 -1
- package/dist/types/sdk/protocol/adapter-validation.d.ts +2 -0
- package/dist/types/sdk/router/session-router.d.ts +11 -0
- package/dist/types/sdk/session.d.ts +16 -0
- package/dist/types/sdk/startup-capability.d.ts +2 -1
- package/dist/types/session/account-inventory.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +24 -7
- package/dist/types/session/credential-store-errors.d.ts +1 -0
- package/dist/types/session/fold-coordinator.d.ts +201 -0
- package/dist/types/session/sdk-run-capability-internal.d.ts +7 -0
- package/dist/types/session/sdk-run-capability.d.ts +2 -0
- package/dist/types/session/session-manager.d.ts +65 -0
- package/dist/types/session/yield-queue.d.ts +5 -4
- package/dist/types/setup/frictionless-onboarding.d.ts +1 -0
- package/dist/types/telemetry/control.d.ts +11 -0
- package/dist/types/telemetry/events.d.ts +20 -0
- package/dist/types/telemetry/index.d.ts +3 -0
- package/dist/types/telemetry/transport.d.ts +22 -0
- package/dist/types/tools/bash-interactive.d.ts +23 -0
- package/dist/types/tools/bash.d.ts +4 -0
- package/dist/types/tools/gh.d.ts +8 -0
- package/dist/types/tools/index.d.ts +10 -5
- package/dist/types/web/search/providers/base.d.ts +9 -4
- package/dist/types/web/search/providers/xai.d.ts +2 -1
- package/dist/types/web/search/types.d.ts +14 -0
- package/package.json +10 -7
- package/scripts/generate-hotkeys-docs.ts +4 -0
- package/scripts/generate-sdk-operation-inventory.ts +9 -3
- package/scripts/verify-gjc-sdk-canonicalization.ts +35 -1
- package/src/async/job-manager.ts +671 -39
- package/src/cli/accounts-cli.ts +9 -5
- package/src/cli/args.ts +17 -0
- package/src/cli/auth-broker-cli.ts +40 -12
- package/src/cli/auth-gateway-cli.ts +273 -97
- package/src/cli/config-cli.ts +2 -2
- package/src/cli/fast-help.ts +4 -0
- package/src/cli/model-presets-cli.ts +100 -0
- package/src/cli/root-flags.ts +5 -0
- package/src/cli/update-cli.ts +117 -16
- package/src/cli.ts +6 -2
- package/src/commands/auth-gateway.ts +12 -8
- package/src/commands/gc.ts +19 -0
- package/src/commands/launch.ts +86 -18
- package/src/commands/model-presets.ts +24 -0
- package/src/commands/sdk.ts +113 -21
- package/src/config/autorouting-tier-map.ts +44 -0
- package/src/config/file-lock-gc.ts +9 -4
- package/src/config/file-lock.ts +890 -99
- package/src/config/internal/model-preset-registry-test-state.ts +28 -0
- package/src/config/internal/model-preset-registry-test-support.ts +11 -0
- package/src/config/keybindings.ts +6 -2
- package/src/config/model-preset-registry.ts +2795 -0
- package/src/config/model-profile-activation.ts +115 -28
- package/src/config/model-profile-contract.ts +4 -1
- package/src/config/model-profiles.ts +56 -14
- package/src/config/model-registry.ts +1044 -122
- package/src/config/model-resolver.ts +5 -0
- package/src/config/settings-schema.ts +19 -1
- package/src/config/settings.ts +11 -1
- package/src/coordinator-mcp/model-preset.ts +32 -17
- package/src/coordinator-mcp/projection-scan.ts +592 -0
- package/src/coordinator-mcp/question-state.ts +9 -11
- package/src/coordinator-mcp/server.ts +111 -42
- package/src/coordinator-mcp/session-reaper.ts +9 -3
- package/src/crash/index-store.ts +2 -0
- package/src/crash/record-loader.ts +10 -0
- package/src/extensibility/extensions/loader.ts +0 -1
- package/src/extensibility/extensions/types.ts +2 -3
- package/src/extensibility/gjc-plugins/runtime-adapters.ts +1 -0
- package/src/gjc-runtime/empty-delete-gc.ts +301 -0
- package/src/gjc-runtime/gc-render.ts +29 -0
- package/src/gjc-runtime/gc-runtime.ts +174 -11
- package/src/gjc-runtime/launch-worktree-reservation.ts +242 -0
- package/src/gjc-runtime/managed-owner-supervisor.ts +63 -51
- package/src/gjc-runtime/session-state-lock.ts +1321 -147
- package/src/gjc-runtime/session-state-sidecar.ts +1004 -25
- package/src/gjc-runtime/tmux-owner-isolation-cli.ts +4 -7
- package/src/gjc-runtime/tmux-owner-isolation.ts +2 -2
- package/src/gjc-runtime/tmux-sessions.ts +252 -13
- package/src/hooks/skill-state.ts +6 -0
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +14 -13
- package/src/internal-urls/issue-pr-protocol.ts +4 -2
- package/src/lsp/config.ts +4 -3
- package/src/lsp/path-trust.ts +12 -6
- package/src/main.ts +66 -4
- 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 +616 -161
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/frictionless-onboarding-selector.ts +48 -2
- package/src/modes/components/jobs-overlay-model.ts +99 -16
- package/src/modes/components/model-selector.ts +158 -18
- package/src/modes/components/oauth-selector.ts +6 -1
- 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 +29 -10
- package/src/modes/components/tool-status-header.ts +151 -15
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +51 -6
- package/src/modes/controllers/selector-controller.ts +191 -96
- package/src/modes/interactive-mode.ts +14 -1
- package/src/modes/jobs-observer.ts +149 -16
- package/src/modes/print-mode.ts +64 -0
- package/src/modes/runtime-init.ts +1 -1
- package/src/modes/ui-language.ts +49 -2
- package/src/modes/utils/injected-user-submission.ts +15 -1
- package/src/prompts/system/master-mode.md +28 -0
- package/src/prompts/tools/bash.md +2 -2
- package/src/prompts/tools/fold-receipt.md +3 -0
- package/src/prompts/tools/job.md +1 -1
- package/src/sdk/acp/adapter.ts +17 -2
- package/src/sdk/broker/broker.ts +1671 -34
- package/src/sdk/broker/endpoint-authority.ts +86 -0
- package/src/sdk/broker/ensure.ts +5 -0
- package/src/sdk/broker/index.ts +3 -0
- package/src/sdk/broker/lifecycle.ts +217 -46
- 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 +548 -0
- package/src/sdk/broker/state-version.ts +26 -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 +46 -5
- package/src/sdk/bus/index.ts +51 -4
- package/src/sdk/bus/kind-aware-reconciliation.ts +36 -6
- package/src/sdk/bus/prompt-reconciliation.ts +28 -4
- package/src/sdk/bus/slack-conversation.ts +5 -1
- 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 +11 -8
- package/src/sdk/cli/session-cli.ts +276 -6
- package/src/sdk/client/client.ts +14 -8
- package/src/sdk/client/discovery.ts +3 -1
- package/src/sdk/host/control/dispatch.ts +101 -37
- 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/host.ts +27 -2
- package/src/sdk/host/model-pin.ts +185 -25
- package/src/sdk/host/sdk-run-capability.ts +1 -0
- package/src/sdk/host/session-runtime.test.ts +581 -43
- package/src/sdk/host/session-runtime.ts +655 -101
- package/src/sdk/lifecycle/broker-client.ts +28 -0
- package/src/sdk/lifecycle/service.ts +279 -20
- package/src/sdk/lifecycle-session.ts +10 -2
- package/src/sdk/prompt-deadline-manager.ts +16 -1
- package/src/sdk/protocol/adapter-validation.ts +27 -0
- package/src/sdk/protocol/operation-inventory.generated.json +53 -2
- package/src/sdk/protocol/operation-registry.ts +17 -4
- package/src/sdk/router/session-router.ts +294 -154
- package/src/sdk/session.ts +440 -167
- package/src/sdk/startup-capability.ts +1 -0
- package/src/session/account-inventory.ts +27 -11
- package/src/session/agent-session.ts +1292 -180
- package/src/session/blob-store.ts +15 -5
- package/src/session/credential-store-errors.ts +2 -0
- package/src/session/fold-coordinator.ts +433 -0
- package/src/session/internal/managed-session-storage.ts +153 -78
- package/src/session/sdk-run-capability-internal.ts +15 -0
- package/src/session/sdk-run-capability.ts +2 -0
- package/src/session/session-manager.ts +342 -28
- package/src/session/yield-queue.ts +24 -10
- package/src/setup/frictionless-onboarding.ts +2 -0
- package/src/setup/provider-presets.json +1 -1
- package/src/slash-commands/helpers/credential-switch.ts +6 -1
- package/src/task/executor.ts +19 -3
- package/src/telemetry/control.ts +26 -0
- package/src/telemetry/events.ts +1268 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/transport.ts +87 -0
- package/src/tools/bash-interactive.ts +182 -85
- package/src/tools/bash.ts +1028 -109
- package/src/tools/gh.ts +104 -48
- package/src/tools/github-cache.ts +2 -2
- package/src/tools/index.ts +11 -5
- package/src/tools/tool-catalog.generated.ts +1 -1
- package/src/web/search/provider.ts +120 -11
- package/src/web/search/providers/anthropic.ts +89 -17
- package/src/web/search/providers/base.ts +9 -4
- package/src/web/search/providers/gemini.ts +87 -15
- package/src/web/search/providers/openai-compatible.ts +23 -8
- package/src/web/search/providers/xai.ts +98 -12
- package/src/web/search/types.ts +16 -0
|
@@ -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/lsp/config.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import
|
|
4
|
+
import * as piUtils from "@gajae-code/utils";
|
|
5
|
+
import { isRecord, logger, pathIsWithin } from "@gajae-code/utils";
|
|
5
6
|
import { YAML } from "bun";
|
|
6
7
|
import { getConfigDirPaths } from "../config";
|
|
7
8
|
import { type ClaudePluginRoot, getPreloadedPluginRoots } from "../discovery/helpers";
|
|
@@ -332,13 +333,13 @@ export function resolveCommand(command: string, cwd: string): string | null {
|
|
|
332
333
|
}
|
|
333
334
|
|
|
334
335
|
// Fall back to $PATH
|
|
335
|
-
return
|
|
336
|
+
return piUtils.$which(command);
|
|
336
337
|
}
|
|
337
338
|
|
|
338
339
|
/** Resolve an LSP executable without consulting project-controlled bin directories. */
|
|
339
340
|
function resolveTrustedLspCommand(command: string, cwd: string): string | null {
|
|
340
341
|
if (!path.isAbsolute(command) && (command.includes("/") || command.includes("\\"))) return null;
|
|
341
|
-
const discovered = path.isAbsolute(command) ? command :
|
|
342
|
+
const discovered = path.isAbsolute(command) ? command : piUtils.$which(command);
|
|
342
343
|
if (!discovered) return null;
|
|
343
344
|
if (isProjectControlledPath(discovered, cwd)) return null;
|
|
344
345
|
const canonical = canonicalExistingPath(discovered);
|
package/src/lsp/path-trust.ts
CHANGED
|
@@ -63,19 +63,25 @@ export function isProjectControlledPath(candidate: string, cwd: string): boolean
|
|
|
63
63
|
const home = os.homedir();
|
|
64
64
|
const stopPaths = new Set([path.resolve(home), canonicalPath(home)].map(normalizePathForComparison));
|
|
65
65
|
const lexicalTrustRoot = findProjectTrustRoot(cwd, stopPaths);
|
|
66
|
-
|
|
66
|
+
const candidateIsHomeScoped = pathIsLexicallyWithin(home, candidate) || pathIsWithin(home, candidate);
|
|
67
|
+
if (
|
|
68
|
+
lexicalTrustRoot !== undefined &&
|
|
69
|
+
pathIsLexicallyWithin(lexicalTrustRoot, candidate) &&
|
|
70
|
+
(!candidateIsHomeScoped || pathIsLexicallyWithin(home, lexicalTrustRoot))
|
|
71
|
+
)
|
|
72
|
+
return true;
|
|
67
73
|
|
|
68
74
|
const canonicalTrustRoots = new Set<string>();
|
|
69
75
|
if (lexicalTrustRoot !== undefined) canonicalTrustRoots.add(canonicalPath(lexicalTrustRoot));
|
|
70
76
|
const canonicalTrustRoot = findProjectTrustRoot(canonicalPath(cwd), stopPaths);
|
|
71
|
-
if (canonicalTrustRoot !== undefined) canonicalTrustRoots.add(canonicalTrustRoot);
|
|
77
|
+
if (canonicalTrustRoot !== undefined) canonicalTrustRoots.add(canonicalPath(canonicalTrustRoot));
|
|
72
78
|
for (const trustRoot of canonicalTrustRoots) {
|
|
73
79
|
if (
|
|
74
|
-
pathIsLexicallyWithin(trustRoot, canonicalParentPath(candidate)) ||
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
(pathIsLexicallyWithin(trustRoot, canonicalParentPath(candidate)) ||
|
|
81
|
+
pathIsWithin(trustRoot, canonicalPath(candidate))) &&
|
|
82
|
+
(!candidateIsHomeScoped || pathIsLexicallyWithin(home, trustRoot) || pathIsWithin(home, trustRoot))
|
|
83
|
+
)
|
|
77
84
|
return true;
|
|
78
|
-
}
|
|
79
85
|
}
|
|
80
86
|
return false;
|
|
81
87
|
}
|
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 {
|
|
@@ -349,7 +351,9 @@ export async function submitInteractiveInput(
|
|
|
349
351
|
| "checkShutdownRequested"
|
|
350
352
|
| "waitForAgentEnd"
|
|
351
353
|
>,
|
|
352
|
-
session: Pick<AgentSession, "prompt" | "promptCustomMessage"
|
|
354
|
+
session: Pick<AgentSession, "prompt" | "promptCustomMessage"> & {
|
|
355
|
+
continuePersistedHistory?: AgentSession["continuePersistedHistory"];
|
|
356
|
+
},
|
|
353
357
|
input: SubmittedUserInput,
|
|
354
358
|
): Promise<void> {
|
|
355
359
|
if (input.cancelled) {
|
|
@@ -357,8 +361,13 @@ export async function submitInteractiveInput(
|
|
|
357
361
|
}
|
|
358
362
|
|
|
359
363
|
try {
|
|
364
|
+
if (input.started) {
|
|
365
|
+
if (!session.continuePersistedHistory) throw new Error("Session continuation is unavailable.");
|
|
366
|
+
await session.continuePersistedHistory();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
360
369
|
// Continue shortcuts submit an already-started empty prompt with no optimistic user message.
|
|
361
|
-
if (!
|
|
370
|
+
if (!mode.markPendingSubmissionStarted(input)) {
|
|
362
371
|
return;
|
|
363
372
|
}
|
|
364
373
|
const agentEnd = mode.waitForAgentEnd();
|
|
@@ -745,6 +754,9 @@ export async function runInteractiveMode(
|
|
|
745
754
|
if (
|
|
746
755
|
shouldOfferAutomaticOnboarding({
|
|
747
756
|
normalInteractive: true,
|
|
757
|
+
automation:
|
|
758
|
+
/^(?:1|true|yes|on)$/i.test(process.env.CI ?? "") ||
|
|
759
|
+
/^(?:1|true|yes|on)$/i.test(process.env.GJC_AUTOMATION ?? ""),
|
|
748
760
|
initialMessage,
|
|
749
761
|
initialMessages,
|
|
750
762
|
initialImages,
|
|
@@ -1359,6 +1371,7 @@ export async function runRootCommand(
|
|
|
1359
1371
|
deps: RunRootCommandDependencies = {},
|
|
1360
1372
|
): Promise<void> {
|
|
1361
1373
|
const parsedArgs = parsed;
|
|
1374
|
+
assertMasterLaunchArgs(parsedArgs);
|
|
1362
1375
|
let initialThemeInitialized = false;
|
|
1363
1376
|
let autoChdirApplied = false;
|
|
1364
1377
|
let bareResumeSessionManager: SessionManager | undefined;
|
|
@@ -1546,6 +1559,22 @@ export async function runRootCommand(
|
|
|
1546
1559
|
print: Boolean(parsedArgs.print),
|
|
1547
1560
|
mode: parsedArgs.mode,
|
|
1548
1561
|
});
|
|
1562
|
+
// Master admission runs unconditionally and BEFORE any session state is
|
|
1563
|
+
// opened or created. Nesting it under nonInteractiveError skipped it for a
|
|
1564
|
+
// non-TTY launch carrying prepared input, which resolves to autoPrint with no
|
|
1565
|
+
// error, so `--master <prompt>` proceeded as an ordinary auto-print run.
|
|
1566
|
+
try {
|
|
1567
|
+
assertMasterLaunchDisposition({
|
|
1568
|
+
master: parsedArgs.master,
|
|
1569
|
+
isInteractive: disposition.isInteractive,
|
|
1570
|
+
autoPrint: disposition.autoPrint,
|
|
1571
|
+
nonInteractiveError: disposition.nonInteractiveError,
|
|
1572
|
+
});
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
process.stderr.write(`${chalk.red(error instanceof Error ? error.message : "Invalid master launch")}\n`);
|
|
1575
|
+
if (!deps.suppressProcessExit) process.exitCode = 1;
|
|
1576
|
+
return;
|
|
1577
|
+
}
|
|
1549
1578
|
if (disposition.nonInteractiveError) {
|
|
1550
1579
|
process.stderr.write(`${chalk.red(disposition.nonInteractiveError)}\n`);
|
|
1551
1580
|
process.exit(1);
|
|
@@ -1669,6 +1698,14 @@ export async function runRootCommand(
|
|
|
1669
1698
|
}
|
|
1670
1699
|
}
|
|
1671
1700
|
}
|
|
1701
|
+
const masterModeContext =
|
|
1702
|
+
parsedArgs.master === true && sessionManager
|
|
1703
|
+
? createMasterModeContext(
|
|
1704
|
+
parsedArgs.masterScope ?? "repo",
|
|
1705
|
+
sessionManager.getSessionId(),
|
|
1706
|
+
newMasterAttestationEpoch(),
|
|
1707
|
+
)
|
|
1708
|
+
: undefined;
|
|
1672
1709
|
|
|
1673
1710
|
const { options: sessionOptions } = await logger.time(
|
|
1674
1711
|
"buildSessionOptions",
|
|
@@ -1735,6 +1772,7 @@ export async function runRootCommand(
|
|
|
1735
1772
|
sessionOptions.notificationHostModeSupported = isInteractive;
|
|
1736
1773
|
sessionOptions.sdkHostModeSupported = isInteractive;
|
|
1737
1774
|
sessionOptions.settings = settingsInstance;
|
|
1775
|
+
sessionOptions.masterModeContext = masterModeContext;
|
|
1738
1776
|
if (isInteractive && sessionOptions.mcpConfigPath) {
|
|
1739
1777
|
sessionOptions.deferMcpConfigStartup = true;
|
|
1740
1778
|
}
|
|
@@ -1819,7 +1857,7 @@ export async function runRootCommand(
|
|
|
1819
1857
|
const directSessionId = process.env.GJC_LIFECYCLE_REQUEST_ID ? undefined : sessionManager?.getSessionId();
|
|
1820
1858
|
if (directSessionId) {
|
|
1821
1859
|
const sessionIndex = new SessionIndex(settingsInstance.getAgentDir());
|
|
1822
|
-
const locator =
|
|
1860
|
+
const locator = await resolveSessionLocator(sessionManager?.getCwd() ?? cwd, settingsInstance.getAgentDir());
|
|
1823
1861
|
// A pid is reusable, so the broker's teardown fence needs this session's OS
|
|
1824
1862
|
// start incarnation recorded alongside the pid it publishes.
|
|
1825
1863
|
const directSessionIncarnation = processIncarnation(process.pid);
|
|
@@ -1830,7 +1868,31 @@ export async function runRootCommand(
|
|
|
1830
1868
|
endpointGeneration: 0,
|
|
1831
1869
|
pid: process.pid,
|
|
1832
1870
|
...(directSessionIncarnation ? { processIncarnation: directSessionIncarnation } : {}),
|
|
1871
|
+
...(masterModeContext && directSessionIncarnation
|
|
1872
|
+
? {
|
|
1873
|
+
masterRole: {
|
|
1874
|
+
version: 2,
|
|
1875
|
+
ownerSessionId: masterModeContext.ownerSessionId,
|
|
1876
|
+
launchPid: process.pid,
|
|
1877
|
+
launchProcessIncarnation: directSessionIncarnation,
|
|
1878
|
+
role: "master" as const,
|
|
1879
|
+
attestationEpoch: masterModeContext.attestationEpoch,
|
|
1880
|
+
},
|
|
1881
|
+
}
|
|
1882
|
+
: {}),
|
|
1833
1883
|
});
|
|
1884
|
+
if (locator.worktreeRoot !== null) {
|
|
1885
|
+
try {
|
|
1886
|
+
await releaseLaunchWorktreeReservationAfterRegistration(
|
|
1887
|
+
settingsInstance.getAgentDir(),
|
|
1888
|
+
locator.worktreeRoot,
|
|
1889
|
+
);
|
|
1890
|
+
} catch (error) {
|
|
1891
|
+
logger.warn("Failed to release launch worktree reservation after host registration", {
|
|
1892
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1893
|
+
});
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1834
1896
|
postmortem.register("direct-session-index", async () => {
|
|
1835
1897
|
await sessionIndex.append({
|
|
1836
1898
|
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
|
+
}
|