@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
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { readEndpointFile } from "./endpoint-authority";
|
|
4
|
+
import {
|
|
5
|
+
type IndexedSession,
|
|
6
|
+
isSessionAuthorityEligible,
|
|
7
|
+
type MasterRoleAttestationV2,
|
|
8
|
+
type SessionIndex,
|
|
9
|
+
} from "./session-index";
|
|
10
|
+
import type { MasterCapabilityVerifier } from "./spawn-authority";
|
|
11
|
+
|
|
12
|
+
const MASTER_CAPABILITY_VERIFY_TIMEOUT_MS = 2_000;
|
|
13
|
+
|
|
14
|
+
export type SessionEndpoint = {
|
|
15
|
+
url: string;
|
|
16
|
+
token: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function processIdentity(record: Pick<IndexedSession, "hostIncarnation" | "processIncarnation">): string | undefined {
|
|
20
|
+
return record.hostIncarnation ?? record.processIncarnation;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function matchesAttestation(left: MasterRoleAttestationV2, right: MasterRoleAttestationV2): boolean {
|
|
24
|
+
return (
|
|
25
|
+
left.version === right.version &&
|
|
26
|
+
left.ownerSessionId === right.ownerSessionId &&
|
|
27
|
+
left.launchPid === right.launchPid &&
|
|
28
|
+
left.launchProcessIncarnation === right.launchProcessIncarnation &&
|
|
29
|
+
left.role === right.role &&
|
|
30
|
+
left.attestationEpoch === right.attestationEpoch
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isEffectiveMasterHost(
|
|
35
|
+
record: IndexedSession,
|
|
36
|
+
ownerSessionId: string,
|
|
37
|
+
attestationEpoch: string,
|
|
38
|
+
): record is IndexedSession & { masterRole: MasterRoleAttestationV2 } {
|
|
39
|
+
const attestation = record.masterRole;
|
|
40
|
+
return (
|
|
41
|
+
record.endpointGeneration > 0 &&
|
|
42
|
+
record.live &&
|
|
43
|
+
!record.terminal &&
|
|
44
|
+
!record.terminalUncertain &&
|
|
45
|
+
isSessionAuthorityEligible(record) &&
|
|
46
|
+
attestation !== undefined &&
|
|
47
|
+
attestation.version === 2 &&
|
|
48
|
+
attestation.role === "master" &&
|
|
49
|
+
attestation.ownerSessionId === ownerSessionId &&
|
|
50
|
+
attestation.attestationEpoch === attestationEpoch &&
|
|
51
|
+
attestation.launchPid === record.pid &&
|
|
52
|
+
attestation.launchProcessIncarnation === processIdentity(record)
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function sameAttachment(left: IndexedSession, right: IndexedSession): boolean {
|
|
57
|
+
return (
|
|
58
|
+
left.sessionId === right.sessionId &&
|
|
59
|
+
left.endpointGeneration === right.endpointGeneration &&
|
|
60
|
+
left.pid === right.pid &&
|
|
61
|
+
left.endpointMtimeMs === right.endpointMtimeMs &&
|
|
62
|
+
left.endpointFileId === right.endpointFileId &&
|
|
63
|
+
left.hostIncarnation === right.hostIncarnation &&
|
|
64
|
+
left.processIncarnation === right.processIncarnation &&
|
|
65
|
+
left.indexSeq === right.indexSeq &&
|
|
66
|
+
left.masterRole !== undefined &&
|
|
67
|
+
right.masterRole !== undefined &&
|
|
68
|
+
matchesAttestation(left.masterRole, right.masterRole)
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function endpointFileId(stat: { dev: bigint; ino: bigint }): string {
|
|
73
|
+
return `${stat.dev}:${stat.ino}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function adoptedDirectAttestation(
|
|
77
|
+
rows: readonly IndexedSession[],
|
|
78
|
+
effective: IndexedSession & { masterRole: MasterRoleAttestationV2 },
|
|
79
|
+
): boolean {
|
|
80
|
+
return rows.some(row => {
|
|
81
|
+
const direct = row.masterRole;
|
|
82
|
+
return (
|
|
83
|
+
row.sessionId === effective.sessionId &&
|
|
84
|
+
row.endpointGeneration === 0 &&
|
|
85
|
+
row.pid === effective.masterRole.launchPid &&
|
|
86
|
+
processIdentity(row) === effective.masterRole.launchProcessIncarnation &&
|
|
87
|
+
direct !== undefined &&
|
|
88
|
+
matchesAttestation(direct, effective.masterRole)
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Reads and validates a registered host's authenticated endpoint record. */
|
|
94
|
+
export async function readEndpoint(record: IndexedSession): Promise<SessionEndpoint | undefined> {
|
|
95
|
+
if (record.endpointMtimeMs === undefined || record.endpointFileId === undefined) return undefined;
|
|
96
|
+
try {
|
|
97
|
+
const endpointPath = path.join(record.locator.stateRoot, "sdk", `${record.sessionId}.json`);
|
|
98
|
+
const file = await readEndpointFile(endpointPath);
|
|
99
|
+
if (!file) return undefined;
|
|
100
|
+
const endpoint = JSON.parse(file.source) as Record<string, unknown>;
|
|
101
|
+
if (
|
|
102
|
+
endpoint.sessionId !== record.sessionId ||
|
|
103
|
+
endpoint.pid !== record.pid ||
|
|
104
|
+
typeof endpoint.url !== "string" ||
|
|
105
|
+
typeof endpoint.token !== "string" ||
|
|
106
|
+
endpoint.url.length === 0 ||
|
|
107
|
+
endpoint.token.length === 0 ||
|
|
108
|
+
endpointFileId(file) !== record.endpointFileId ||
|
|
109
|
+
Math.abs(file.mtimeMs - record.endpointMtimeMs) > 0.001
|
|
110
|
+
)
|
|
111
|
+
return undefined;
|
|
112
|
+
const url = new URL(endpoint.url);
|
|
113
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") return undefined;
|
|
114
|
+
return { url: endpoint.url, token: endpoint.token };
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function verifyOverAttachment(input: {
|
|
121
|
+
endpoint: SessionEndpoint;
|
|
122
|
+
nonce: string;
|
|
123
|
+
attestationEpoch: string;
|
|
124
|
+
capability: string;
|
|
125
|
+
}): Promise<boolean> {
|
|
126
|
+
const settled = Promise.withResolvers<boolean>();
|
|
127
|
+
let complete = false;
|
|
128
|
+
const settle = (allowed: boolean): void => {
|
|
129
|
+
if (complete) return;
|
|
130
|
+
complete = true;
|
|
131
|
+
settled.resolve(allowed);
|
|
132
|
+
};
|
|
133
|
+
let socket: WebSocket | undefined;
|
|
134
|
+
try {
|
|
135
|
+
const url = new URL(input.endpoint.url);
|
|
136
|
+
url.searchParams.set("token", input.endpoint.token);
|
|
137
|
+
socket = new WebSocket(url);
|
|
138
|
+
const onClose = (): void => {
|
|
139
|
+
settle(false);
|
|
140
|
+
};
|
|
141
|
+
const onError = (): void => {
|
|
142
|
+
settle(false);
|
|
143
|
+
};
|
|
144
|
+
const onMessage = (event: MessageEvent): void => {
|
|
145
|
+
let frame: Record<string, unknown>;
|
|
146
|
+
try {
|
|
147
|
+
const parsed = JSON.parse(String(event.data));
|
|
148
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return;
|
|
149
|
+
frame = parsed as Record<string, unknown>;
|
|
150
|
+
} catch {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (frame.type === "hello") {
|
|
154
|
+
try {
|
|
155
|
+
socket?.send(JSON.stringify({ type: "hello", protocolVersion: 3, capabilities: [] }));
|
|
156
|
+
socket?.send(
|
|
157
|
+
JSON.stringify({
|
|
158
|
+
type: "master_capability_verify",
|
|
159
|
+
id: input.nonce,
|
|
160
|
+
nonce: input.nonce,
|
|
161
|
+
attestationEpoch: input.attestationEpoch,
|
|
162
|
+
capability: input.capability,
|
|
163
|
+
}),
|
|
164
|
+
);
|
|
165
|
+
} catch {
|
|
166
|
+
settle(false);
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (frame.type !== "master_capability_verify_result") return;
|
|
171
|
+
settle(
|
|
172
|
+
frame.id === input.nonce &&
|
|
173
|
+
frame.ok === true &&
|
|
174
|
+
frame.nonce === input.nonce &&
|
|
175
|
+
frame.attestationEpoch === input.attestationEpoch,
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
socket.addEventListener("error", onError);
|
|
179
|
+
socket.addEventListener("close", onClose);
|
|
180
|
+
socket.addEventListener("message", onMessage);
|
|
181
|
+
return await Promise.race([settled.promise, Bun.sleep(MASTER_CAPABILITY_VERIFY_TIMEOUT_MS).then(() => false)]);
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
} finally {
|
|
185
|
+
try {
|
|
186
|
+
socket?.close();
|
|
187
|
+
} catch {
|
|
188
|
+
// Closing an already failed attachment is best effort.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Creates the ephemeral effective-host verifier used by broker spawn admission. */
|
|
194
|
+
export function createMasterCapabilityVerifier(index: SessionIndex): MasterCapabilityVerifier {
|
|
195
|
+
return {
|
|
196
|
+
async verifyMasterCapability(
|
|
197
|
+
ownerSessionId: string,
|
|
198
|
+
rawCapability: string,
|
|
199
|
+
attestationEpoch: string,
|
|
200
|
+
): Promise<{ allowed: boolean }> {
|
|
201
|
+
try {
|
|
202
|
+
await index.refresh();
|
|
203
|
+
const rows = index.listSessionIdentities();
|
|
204
|
+
const effective = rows.filter(row => isEffectiveMasterHost(row, ownerSessionId, attestationEpoch));
|
|
205
|
+
if (effective.length !== 1) return { allowed: false };
|
|
206
|
+
const attachment = effective[0]!;
|
|
207
|
+
if (!adoptedDirectAttestation(rows, attachment)) return { allowed: false };
|
|
208
|
+
const endpoint = await readEndpoint(attachment);
|
|
209
|
+
if (!endpoint) return { allowed: false };
|
|
210
|
+
await index.refresh();
|
|
211
|
+
const current = index.listSessionIdentities().find(row => sameAttachment(attachment, row));
|
|
212
|
+
if (!current || !isEffectiveMasterHost(current, ownerSessionId, attestationEpoch))
|
|
213
|
+
return { allowed: false };
|
|
214
|
+
const nonce = randomBytes(32).toString("base64url");
|
|
215
|
+
const allowed = await verifyOverAttachment({
|
|
216
|
+
endpoint,
|
|
217
|
+
nonce,
|
|
218
|
+
attestationEpoch,
|
|
219
|
+
capability: rawCapability,
|
|
220
|
+
});
|
|
221
|
+
await index.refresh();
|
|
222
|
+
const after = index.listSessionIdentities().find(row => sameAttachment(attachment, row));
|
|
223
|
+
return {
|
|
224
|
+
allowed:
|
|
225
|
+
allowed && after !== undefined && isEffectiveMasterHost(after, ownerSessionId, attestationEpoch),
|
|
226
|
+
};
|
|
227
|
+
} catch {
|
|
228
|
+
return { allowed: false };
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
1
|
+
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import * as fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { logger, resolveEquivalentPath } from "@gajae-code/utils";
|
|
5
5
|
import { nativeProcessBindings } from "@gajae-code/utils/native-process";
|
|
6
6
|
import { withFileLock } from "../../config/file-lock";
|
|
7
|
+
import { repo } from "../../utils/git";
|
|
7
8
|
import { processIncarnation } from "./process-incarnation";
|
|
8
9
|
import {
|
|
10
|
+
assertSupportedSessionIndexEventVersion,
|
|
9
11
|
assertSupportedSnapshotVersion,
|
|
10
|
-
assertSupportedStateVersion,
|
|
11
12
|
SDK_STATE_VERSION,
|
|
13
|
+
SESSION_INDEX_EVENT_VERSION,
|
|
12
14
|
SESSION_INDEX_SNAPSHOT_VERSION,
|
|
13
15
|
UnsupportedStateVersionError,
|
|
14
16
|
} from "./state-version";
|
|
@@ -53,12 +55,79 @@ export interface SessionIndexProcessObservation {
|
|
|
53
55
|
readonly incarnation: string;
|
|
54
56
|
readonly isRunning: () => boolean;
|
|
55
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Canonical location facts for one SDK session.
|
|
60
|
+
*
|
|
61
|
+
* `cwd` is always a realpath-canonicalized directory. `worktreeRoot` is the
|
|
62
|
+
* canonical Git worktree root when `cwd` is in a worktree, otherwise `null`.
|
|
63
|
+
* `stateRoot` retains its host-provided authority spelling and is never derived
|
|
64
|
+
* from either canonical path.
|
|
65
|
+
*/
|
|
66
|
+
export type SessionLocatorV2 = {
|
|
67
|
+
cwd: string;
|
|
68
|
+
worktreeRoot: string | null;
|
|
69
|
+
stateRoot: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Durable master-role facts. `attestationEpoch` is opaque random broker state. */
|
|
73
|
+
export type MasterRoleAttestationV2 = {
|
|
74
|
+
version: 2;
|
|
75
|
+
ownerSessionId: string;
|
|
76
|
+
launchPid: number;
|
|
77
|
+
launchProcessIncarnation: string;
|
|
78
|
+
role: "master";
|
|
79
|
+
attestationEpoch: string;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export function newMasterAttestationEpoch(): string {
|
|
83
|
+
return randomBytes(32).toString("base64url");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Canonicalize a cwd without making an unavailable path a launch failure. */
|
|
87
|
+
export async function canonicalSessionCwd(cwd: string): Promise<string> {
|
|
88
|
+
try {
|
|
89
|
+
return await fs.realpath(cwd);
|
|
90
|
+
} catch {
|
|
91
|
+
return path.resolve(cwd);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Resolve the canonical worktree root; non-Git and probe failures are `null`. */
|
|
96
|
+
export async function sessionWorktreeRoot(cwd: string): Promise<string | null> {
|
|
97
|
+
try {
|
|
98
|
+
const repository = await repo.resolve(cwd);
|
|
99
|
+
return repository ? await canonicalSessionCwd(repository.repoRoot) : null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Build the only permitted locator shape for a newly registered session. */
|
|
106
|
+
export async function resolveSessionLocator(cwd: string, stateRoot: string): Promise<SessionLocatorV2> {
|
|
107
|
+
const canonicalCwd = await canonicalSessionCwd(cwd);
|
|
108
|
+
return { cwd: canonicalCwd, worktreeRoot: await sessionWorktreeRoot(canonicalCwd), stateRoot };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function sessionLocatorV2(locator: unknown): locator is SessionLocatorV2 {
|
|
112
|
+
if (typeof locator !== "object" || locator === null || Array.isArray(locator)) return false;
|
|
113
|
+
const keys = Object.keys(locator);
|
|
114
|
+
if (keys.length !== 3 || !keys.every(key => key === "cwd" || key === "worktreeRoot" || key === "stateRoot"))
|
|
115
|
+
return false;
|
|
116
|
+
return (
|
|
117
|
+
typeof (locator as { cwd?: unknown }).cwd === "string" &&
|
|
118
|
+
(locator as { cwd: string }).cwd.length > 0 &&
|
|
119
|
+
(typeof (locator as { worktreeRoot?: unknown }).worktreeRoot === "string" ||
|
|
120
|
+
(locator as { worktreeRoot?: unknown }).worktreeRoot === null) &&
|
|
121
|
+
typeof (locator as { stateRoot?: unknown }).stateRoot === "string" &&
|
|
122
|
+
(locator as { stateRoot: string }).stateRoot.length > 0
|
|
123
|
+
);
|
|
124
|
+
}
|
|
56
125
|
export interface SessionIndexEvent {
|
|
57
|
-
version: typeof SDK_STATE_VERSION;
|
|
126
|
+
version: typeof SDK_STATE_VERSION | typeof SESSION_INDEX_EVENT_VERSION;
|
|
58
127
|
indexSeq: number;
|
|
59
128
|
type: SessionIndexEventType;
|
|
60
129
|
sessionId: string;
|
|
61
|
-
locator:
|
|
130
|
+
locator: SessionLocatorV2;
|
|
62
131
|
endpointGeneration: number;
|
|
63
132
|
pid: number;
|
|
64
133
|
/**
|
|
@@ -69,23 +138,28 @@ export interface SessionIndexEvent {
|
|
|
69
138
|
*/
|
|
70
139
|
processIncarnation?: string;
|
|
71
140
|
endpointMtimeMs?: number;
|
|
141
|
+
/** Immutable endpoint file identity captured by the broker at registration. */
|
|
142
|
+
endpointFileId?: string;
|
|
72
143
|
lifecycleRequestId?: string;
|
|
73
144
|
terminalUncertain?: boolean;
|
|
74
145
|
/** OS process incarnation (C1); absent on legacy v1/v2 events. */
|
|
75
146
|
hostIncarnation?: string;
|
|
76
147
|
/** Present on host_heartbeat checkpoints (C2). */
|
|
77
148
|
activity?: SessionActivity;
|
|
149
|
+
masterRole?: MasterRoleAttestationV2;
|
|
78
150
|
ts: number;
|
|
79
151
|
checksum: string;
|
|
80
152
|
}
|
|
81
153
|
export interface IndexedSession {
|
|
82
154
|
sessionId: string;
|
|
83
|
-
locator:
|
|
155
|
+
locator: SessionLocatorV2;
|
|
84
156
|
endpointGeneration: number;
|
|
85
157
|
pid: number;
|
|
86
158
|
/** OS start incarnation of `pid` as published by its own host at registration. */
|
|
87
159
|
processIncarnation?: string;
|
|
88
160
|
endpointMtimeMs?: number;
|
|
161
|
+
/** Immutable endpoint file identity captured by the broker at registration. */
|
|
162
|
+
endpointFileId?: string;
|
|
89
163
|
live: boolean;
|
|
90
164
|
indexSeq: number;
|
|
91
165
|
lifecycleRequestId?: string;
|
|
@@ -95,6 +169,7 @@ export interface IndexedSession {
|
|
|
95
169
|
activity?: SessionActivity;
|
|
96
170
|
/** Wall-clock timestamp of the latest admitted heartbeat, when one exists. */
|
|
97
171
|
lastHeartbeatAt?: number;
|
|
172
|
+
masterRole?: MasterRoleAttestationV2;
|
|
98
173
|
/** True when more than one unresolved authority-fencing state-root identity claims this session id. */
|
|
99
174
|
ambiguous: boolean;
|
|
100
175
|
/** True when the identity's latest event is terminal (DR-1 retains stopped rows for inspection/offline tail). */
|
|
@@ -164,7 +239,10 @@ interface SessionIndexScan {
|
|
|
164
239
|
}
|
|
165
240
|
|
|
166
241
|
/** Admission-fence rejection codes recorded in the durable index audit (C5/C4). */
|
|
167
|
-
export type SessionIndexAuditCode =
|
|
242
|
+
export type SessionIndexAuditCode =
|
|
243
|
+
| "rejected_superseded_incarnation"
|
|
244
|
+
| "rejected_after_tombstone"
|
|
245
|
+
| "rejected_legacy_locator";
|
|
168
246
|
export interface SessionIndexAuditRecord {
|
|
169
247
|
version: typeof SDK_STATE_VERSION;
|
|
170
248
|
code: SessionIndexAuditCode;
|
|
@@ -268,6 +346,14 @@ const tupleKey = (event: SessionIndexEvent) =>
|
|
|
268
346
|
const effectiveIncarnation = (event: SessionIndexEvent) => event.hostIncarnation ?? event.processIncarnation;
|
|
269
347
|
const identityKey = (event: SessionIndexEvent) => `${tupleKey(event)}\u0000${effectiveIncarnation(event) ?? ""}`;
|
|
270
348
|
|
|
349
|
+
function hasSessionLocatorV2(event: SessionIndexEvent): boolean {
|
|
350
|
+
return sessionLocatorV2(event.locator);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function legacyLocatorDiagnostic(event: SessionIndexEvent): string {
|
|
354
|
+
return `Session ${event.sessionId} has a legacy locator row and must re-register.`;
|
|
355
|
+
}
|
|
356
|
+
|
|
271
357
|
interface ResolvedRetentionPolicy {
|
|
272
358
|
clock: () => number;
|
|
273
359
|
maxAgeMs: number;
|
|
@@ -305,7 +391,17 @@ interface Admission {
|
|
|
305
391
|
*/
|
|
306
392
|
function admitEvents(events: SessionIndexEvent[]): Admission {
|
|
307
393
|
const authoritative = new Map<string, { incarnation: string | undefined; indexSeq: number }>();
|
|
394
|
+
const rejected: RejectedEvent[] = [];
|
|
308
395
|
for (const event of events) {
|
|
396
|
+
if (!hasSessionLocatorV2(event)) {
|
|
397
|
+
rejected.push({
|
|
398
|
+
code: "rejected_legacy_locator",
|
|
399
|
+
event,
|
|
400
|
+
supersededByIncarnation: undefined,
|
|
401
|
+
supersededByIndexSeq: event.indexSeq,
|
|
402
|
+
});
|
|
403
|
+
continue;
|
|
404
|
+
}
|
|
309
405
|
if (event.type !== "host_registered") continue;
|
|
310
406
|
const key = tupleKey(event);
|
|
311
407
|
const current = authoritative.get(key);
|
|
@@ -314,8 +410,8 @@ function admitEvents(events: SessionIndexEvent[]): Admission {
|
|
|
314
410
|
}
|
|
315
411
|
}
|
|
316
412
|
const admitted: SessionIndexEvent[] = [];
|
|
317
|
-
const rejected: RejectedEvent[] = [];
|
|
318
413
|
for (const event of events) {
|
|
414
|
+
if (!hasSessionLocatorV2(event)) continue;
|
|
319
415
|
const authority = authoritative.get(tupleKey(event));
|
|
320
416
|
if (
|
|
321
417
|
authority !== undefined &&
|
|
@@ -378,7 +474,7 @@ function auditRecords(events: SessionIndexEvent[], ts: number): SessionIndexAudi
|
|
|
378
474
|
indexSeq: rejection.event.indexSeq,
|
|
379
475
|
sessionId: rejection.event.sessionId,
|
|
380
476
|
endpointGeneration: rejection.event.endpointGeneration,
|
|
381
|
-
stateRoot: rejection.event.locator.stateRoot,
|
|
477
|
+
stateRoot: hasSessionLocatorV2(rejection.event) ? rejection.event.locator.stateRoot : "unknown",
|
|
382
478
|
...(rejection.event.hostIncarnation !== undefined ? { hostIncarnation: rejection.event.hostIncarnation } : {}),
|
|
383
479
|
...(rejection.supersededByIncarnation !== undefined
|
|
384
480
|
? { supersededByIncarnation: rejection.supersededByIncarnation }
|
|
@@ -499,10 +595,12 @@ function projectIdentity(
|
|
|
499
595
|
pid: latest.pid,
|
|
500
596
|
processIncarnation: latest.processIncarnation,
|
|
501
597
|
endpointMtimeMs: latest.endpointMtimeMs,
|
|
598
|
+
endpointFileId: latest.endpointFileId,
|
|
502
599
|
lifecycleRequestId: latest.lifecycleRequestId,
|
|
503
600
|
terminalUncertain,
|
|
504
601
|
indexSeq: latest.indexSeq,
|
|
505
602
|
hostIncarnation: latest.hostIncarnation,
|
|
603
|
+
masterRole: latest.masterRole,
|
|
506
604
|
identityProvenance: recordedIncarnation === undefined ? "legacy" : "composite",
|
|
507
605
|
activity: heartbeat?.activity,
|
|
508
606
|
lastHeartbeatAt: heartbeat?.ts,
|
|
@@ -1140,6 +1238,9 @@ export class SessionIndex {
|
|
|
1140
1238
|
if (scan.diagnosis.status === "unsupported") throw scan.unsupportedError!;
|
|
1141
1239
|
this.#events = [...scan.snapshotEvents, ...scan.validLogEvents];
|
|
1142
1240
|
this.#warnings = [];
|
|
1241
|
+
for (const event of this.#events) {
|
|
1242
|
+
if (!hasSessionLocatorV2(event)) this.#warn(legacyLocatorDiagnostic(event));
|
|
1243
|
+
}
|
|
1143
1244
|
this.#logOffset = scan.logContents?.length ?? 0;
|
|
1144
1245
|
this.#corruptSuffix = scan.diagnosis.status === "corrupt";
|
|
1145
1246
|
if (scan.diagnosis.reason === "invalid snapshot") this.#warnings.push("Invalid session index snapshot");
|
|
@@ -1195,7 +1296,7 @@ export class SessionIndex {
|
|
|
1195
1296
|
if (Array.isArray(snapshot.events)) {
|
|
1196
1297
|
try {
|
|
1197
1298
|
for (const event of snapshot.events) {
|
|
1198
|
-
|
|
1299
|
+
assertSupportedSessionIndexEventVersion(snapshotFor(this.#agentDir), event);
|
|
1199
1300
|
supportedEvents.push(event as SessionIndexEvent);
|
|
1200
1301
|
}
|
|
1201
1302
|
} catch (error) {
|
|
@@ -1234,7 +1335,7 @@ export class SessionIndex {
|
|
|
1234
1335
|
if (!line) continue;
|
|
1235
1336
|
try {
|
|
1236
1337
|
const event = JSON.parse(line) as SessionIndexEvent;
|
|
1237
|
-
|
|
1338
|
+
assertSupportedSessionIndexEventVersion(logFor(this.#agentDir), event);
|
|
1238
1339
|
const { checksum, ...unsigned } = event;
|
|
1239
1340
|
if (checksum !== sessionIndexChecksum(unsigned)) {
|
|
1240
1341
|
corrupt = true;
|
|
@@ -1401,7 +1502,7 @@ export class SessionIndex {
|
|
|
1401
1502
|
let event: SessionIndexEvent;
|
|
1402
1503
|
try {
|
|
1403
1504
|
event = JSON.parse(line) as SessionIndexEvent;
|
|
1404
|
-
|
|
1505
|
+
assertSupportedSessionIndexEventVersion(logFor(this.#agentDir), event);
|
|
1405
1506
|
} catch (error) {
|
|
1406
1507
|
if (error instanceof UnsupportedStateVersionError) throw error;
|
|
1407
1508
|
corrupt = true;
|
|
@@ -1485,7 +1586,7 @@ export class SessionIndex {
|
|
|
1485
1586
|
}
|
|
1486
1587
|
const unsigned: Omit<SessionIndexEvent, "checksum"> = {
|
|
1487
1588
|
...input,
|
|
1488
|
-
version:
|
|
1589
|
+
version: SESSION_INDEX_EVENT_VERSION,
|
|
1489
1590
|
indexSeq: this.indexSeq + 1,
|
|
1490
1591
|
ts: input.ts ?? Date.now(),
|
|
1491
1592
|
};
|
|
@@ -1535,7 +1636,7 @@ export class SessionIndex {
|
|
|
1535
1636
|
session.processIncarnation === expected.processIncarnation &&
|
|
1536
1637
|
(session.hostIncarnation ?? session.processIncarnation) ===
|
|
1537
1638
|
(expected.hostIncarnation ?? expected.processIncarnation) &&
|
|
1538
|
-
resolveEquivalentPath(session.locator.
|
|
1639
|
+
resolveEquivalentPath(session.locator.cwd) === resolveEquivalentPath(expected.locator.cwd) &&
|
|
1539
1640
|
path.resolve(session.locator.stateRoot) === path.resolve(expected.locator.stateRoot),
|
|
1540
1641
|
);
|
|
1541
1642
|
let currentRoot: IndexedSession | undefined;
|
|
@@ -1543,7 +1644,7 @@ export class SessionIndex {
|
|
|
1543
1644
|
if (
|
|
1544
1645
|
session.sessionId !== expected.sessionId ||
|
|
1545
1646
|
session.terminal ||
|
|
1546
|
-
resolveEquivalentPath(session.locator.
|
|
1647
|
+
resolveEquivalentPath(session.locator.cwd) !== resolveEquivalentPath(expected.locator.cwd) ||
|
|
1547
1648
|
path.resolve(session.locator.stateRoot) !== path.resolve(expected.locator.stateRoot)
|
|
1548
1649
|
)
|
|
1549
1650
|
continue;
|
|
@@ -1564,7 +1665,7 @@ export class SessionIndex {
|
|
|
1564
1665
|
)
|
|
1565
1666
|
return false;
|
|
1566
1667
|
const unsigned: Omit<SessionIndexEvent, "checksum"> = {
|
|
1567
|
-
version:
|
|
1668
|
+
version: SESSION_INDEX_EVENT_VERSION,
|
|
1568
1669
|
indexSeq: this.indexSeq + 1,
|
|
1569
1670
|
ts: Date.now(),
|
|
1570
1671
|
type: "host_unregistered",
|
|
@@ -2100,7 +2201,7 @@ export class SessionIndex {
|
|
|
2100
2201
|
const current = probed.get(`${row.sessionId}\u0000${row.endpointGeneration}\u0000${row.pid}`);
|
|
2101
2202
|
if (current === undefined || current !== recordedIncarnation) continue;
|
|
2102
2203
|
const unsigned: Omit<SessionIndexEvent, "checksum"> = {
|
|
2103
|
-
version:
|
|
2204
|
+
version: SESSION_INDEX_EVENT_VERSION,
|
|
2104
2205
|
indexSeq: this.indexSeq + events.length + 1,
|
|
2105
2206
|
type: "host_heartbeat",
|
|
2106
2207
|
sessionId: row.sessionId,
|
|
@@ -2109,6 +2210,7 @@ export class SessionIndex {
|
|
|
2109
2210
|
pid: row.pid,
|
|
2110
2211
|
...(row.processIncarnation === undefined ? {} : { processIncarnation: row.processIncarnation }),
|
|
2111
2212
|
...(row.hostIncarnation === undefined ? {} : { hostIncarnation: row.hostIncarnation }),
|
|
2213
|
+
...(row.masterRole === undefined ? {} : { masterRole: row.masterRole }),
|
|
2112
2214
|
activity: { state: "active", at: now },
|
|
2113
2215
|
ts: now,
|
|
2114
2216
|
};
|
|
@@ -2145,7 +2247,7 @@ export class SessionIndex {
|
|
|
2145
2247
|
item.sessionId === registration.sessionId &&
|
|
2146
2248
|
item.endpointGeneration === registration.endpointGeneration &&
|
|
2147
2249
|
item.pid === registration.pid &&
|
|
2148
|
-
resolveEquivalentPath(item.locator.
|
|
2250
|
+
resolveEquivalentPath(item.locator.cwd) === resolveEquivalentPath(registration.locator.cwd) &&
|
|
2149
2251
|
path.resolve(item.locator.stateRoot) === path.resolve(registration.locator.stateRoot) &&
|
|
2150
2252
|
(lifecycleRequestId === undefined || item.lifecycleRequestId === lifecycleRequestId) &&
|
|
2151
2253
|
(incarnation === undefined || (item.hostIncarnation ?? item.processIncarnation) === incarnation),
|
|
@@ -2180,10 +2282,12 @@ export class SessionIndex {
|
|
|
2180
2282
|
pid: event.pid,
|
|
2181
2283
|
processIncarnation: event.processIncarnation,
|
|
2182
2284
|
endpointMtimeMs: event.endpointMtimeMs,
|
|
2285
|
+
endpointFileId: event.endpointFileId,
|
|
2183
2286
|
lifecycleRequestId: event.lifecycleRequestId,
|
|
2184
2287
|
terminalUncertain: false,
|
|
2185
2288
|
indexSeq: event.indexSeq,
|
|
2186
2289
|
hostIncarnation: event.hostIncarnation,
|
|
2290
|
+
masterRole: event.masterRole,
|
|
2187
2291
|
identityProvenance: event.hostIncarnation === undefined ? "legacy" : "composite",
|
|
2188
2292
|
ambiguous: false,
|
|
2189
2293
|
live: alive(event.pid),
|