harnery 0.37.0 → 0.38.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/dist/commander.d.ts +9 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commander.js +7 -2
- package/dist/commands/admission.d.ts +21 -0
- package/dist/commands/admission.d.ts.map +1 -0
- package/dist/commands/admission.js +565 -0
- package/dist/commands/agents.d.ts +7 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +61 -1
- package/dist/commands/artifacts.d.ts.map +1 -1
- package/dist/commands/artifacts.js +48 -2
- package/dist/commands/browse-ai.d.ts +2 -2
- package/dist/commands/browse-ai.d.ts.map +1 -1
- package/dist/commands/browse-ai.js +6 -4
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +349 -21
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/qa-record.d.ts +144 -0
- package/dist/commands/qa-record.d.ts.map +1 -0
- package/dist/commands/qa-record.js +0 -0
- package/dist/commands/qa-run.d.ts +7 -3
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +268 -13
- package/dist/commands/qa-status.d.ts +71 -0
- package/dist/commands/qa-status.d.ts.map +1 -0
- package/dist/commands/qa-status.js +490 -0
- package/dist/commands/qa-verify.d.ts +40 -0
- package/dist/commands/qa-verify.d.ts.map +1 -0
- package/dist/commands/qa-verify.js +180 -0
- package/dist/commands/review-pack.d.ts +4 -0
- package/dist/commands/review-pack.d.ts.map +1 -0
- package/dist/commands/review-pack.js +1001 -0
- package/dist/core/agents/qa-signal.d.ts +111 -0
- package/dist/core/agents/qa-signal.d.ts.map +1 -0
- package/dist/core/agents/qa-signal.js +231 -0
- package/dist/core/agents/session-name-display.d.ts +20 -5
- package/dist/core/agents/session-name-display.d.ts.map +1 -1
- package/dist/core/agents/session-name-display.js +67 -7
- package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
- package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
- package/dist/core/agents/state/heartbeat-writer.js +17 -0
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +1 -0
- package/dist/core/agents/state/live-coordination-writer.js +5 -0
- package/dist/core/artifacts/constants.d.ts +1 -1
- package/dist/core/artifacts/constants.js +1 -1
- package/dist/core/artifacts/index.d.ts +57 -6
- package/dist/core/artifacts/index.d.ts.map +1 -1
- package/dist/core/artifacts/index.js +265 -10
- package/dist/core/config.d.ts +8 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +16 -0
- package/dist/core/diagnostics/bundle.d.ts +16 -0
- package/dist/core/diagnostics/bundle.d.ts.map +1 -1
- package/dist/core/diagnostics/bundle.js +101 -7
- package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
- package/dist/core/events/v3/bootstrap.js +10 -0
- package/dist/core/events/v3/coordination-view.d.ts +3 -0
- package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
- package/dist/core/events/v3/coordination-view.js +69 -8
- package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
- package/dist/core/events/v3/producers/intake.js +9 -2
- package/dist/core/events/v3/producers/recorder.d.ts +23 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +264 -18
- package/dist/core/hooks/cli.js +120 -27
- package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
- package/dist/core/hooks/resolve/transcript.js +10 -3
- package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
- package/dist/core/hooks/session-name-presence.js +4 -1
- package/dist/core/qa-artifacts.d.ts +20 -0
- package/dist/core/qa-artifacts.d.ts.map +1 -0
- package/dist/core/qa-artifacts.js +110 -0
- package/dist/core/resources/contract.d.ts +6 -0
- package/dist/core/resources/contract.d.ts.map +1 -1
- package/dist/core/resources/sampler.d.ts +7 -0
- package/dist/core/resources/sampler.d.ts.map +1 -1
- package/dist/core/resources/sampler.js +106 -5
- package/dist/lib/admission.d.ts +71 -0
- package/dist/lib/admission.d.ts.map +1 -0
- package/dist/lib/admission.js +264 -0
- package/dist/lib/agent-browser/client.d.ts +1 -1
- package/dist/lib/agent-browser/client.d.ts.map +1 -1
- package/dist/lib/agent-browser/client.js +1 -5
- package/dist/lib/browser/capture-fidelity.d.ts +39 -0
- package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
- package/dist/lib/browser/capture-fidelity.js +84 -0
- package/dist/lib/browser/client.d.ts +41 -1
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +167 -9
- package/dist/lib/browser/critique.d.ts +38 -1
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +34 -6
- package/dist/lib/browser/index.d.ts +4 -2
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +2 -0
- package/dist/lib/browser/page-review-judge.d.ts +64 -0
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
- package/dist/lib/browser/page-review-judge.js +270 -0
- package/dist/lib/browser/page-review-pack.d.ts +613 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
- package/dist/lib/browser/page-review-pack.js +1751 -0
- package/dist/lib/browser/qa-run-contracts.d.ts +214 -10
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +136 -1
- package/dist/lib/browser/qa-run.d.ts +100 -10
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +768 -169
- package/dist/lib/browser/request-diagnostics.d.ts +13 -0
- package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
- package/dist/lib/browser/request-diagnostics.js +18 -0
- package/dist/lib/browser/tiling.d.ts +19 -0
- package/dist/lib/browser/tiling.d.ts.map +1 -1
- package/dist/lib/browser/tiling.js +28 -0
- package/dist/lib/cookies/client.d.ts +9 -0
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +197 -44
- package/dist/lib/cookies/extra.d.ts +18 -0
- package/dist/lib/cookies/extra.d.ts.map +1 -0
- package/dist/lib/cookies/extra.js +14 -0
- package/dist/lib/cookies/index.d.ts +2 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +2 -1
- package/dist/lib/durable-job.d.ts +124 -0
- package/dist/lib/durable-job.d.ts.map +1 -0
- package/dist/lib/durable-job.js +296 -0
- package/dist/lib/http/client.d.ts +7 -1
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +2 -0
- package/dist/lib/instructions/templates.d.ts.map +1 -1
- package/dist/lib/instructions/templates.js +5 -2
- package/package.json +8 -2
- package/src/commander.ts +50 -2
- package/src/commands/admission.ts +699 -0
- package/src/commands/agents.ts +87 -1
- package/src/commands/artifacts.ts +97 -21
- package/src/commands/browse-ai.ts +10 -5
- package/src/commands/browse.ts +481 -20
- package/src/commands/fetch.ts +1 -0
- package/src/commands/qa-record.ts +682 -0
- package/src/commands/qa-run.ts +335 -16
- package/src/commands/qa-status.ts +608 -0
- package/src/commands/qa-verify.ts +238 -0
- package/src/commands/review-pack.ts +1281 -0
- package/src/core/agents/qa-signal.ts +261 -0
- package/src/core/agents/session-name-display.ts +78 -7
- package/src/core/agents/state/heartbeat-reader.ts +7 -0
- package/src/core/agents/state/heartbeat-writer.ts +23 -0
- package/src/core/agents/state/live-coordination-view.ts +1 -0
- package/src/core/agents/state/live-coordination-writer.ts +5 -0
- package/src/core/artifacts/constants.ts +1 -1
- package/src/core/artifacts/index.ts +370 -21
- package/src/core/config.ts +23 -0
- package/src/core/diagnostics/bundle.ts +119 -11
- package/src/core/events/v3/bootstrap.ts +10 -0
- package/src/core/events/v3/coordination-view.ts +100 -11
- package/src/core/events/v3/producers/intake.ts +9 -2
- package/src/core/events/v3/producers/recorder.ts +312 -18
- package/src/core/hooks/cli.ts +140 -32
- package/src/core/hooks/resolve/transcript.ts +10 -3
- package/src/core/hooks/session-name-presence.ts +6 -1
- package/src/core/qa-artifacts.ts +126 -0
- package/src/core/resources/contract.ts +7 -0
- package/src/core/resources/sampler.ts +138 -6
- package/src/lib/admission.ts +347 -0
- package/src/lib/agent-browser/client.ts +2 -10
- package/src/lib/browser/capture-fidelity.ts +98 -0
- package/src/lib/browser/client.ts +206 -10
- package/src/lib/browser/critique.ts +62 -7
- package/src/lib/browser/index.ts +36 -0
- package/src/lib/browser/page-review-judge.ts +360 -0
- package/src/lib/browser/page-review-pack.ts +2384 -0
- package/src/lib/browser/qa-run-contracts.ts +366 -3
- package/src/lib/browser/qa-run.ts +868 -190
- package/src/lib/browser/request-diagnostics.ts +27 -0
- package/src/lib/browser/tiling.ts +32 -0
- package/src/lib/cookies/client.ts +228 -42
- package/src/lib/cookies/extra.ts +28 -0
- package/src/lib/cookies/index.ts +2 -0
- package/src/lib/durable-job.ts +407 -0
- package/src/lib/http/client.ts +13 -1
- package/src/lib/instructions/templates.ts +5 -2
|
@@ -695,7 +695,7 @@ function processHookSignalLocked(
|
|
|
695
695
|
const durability = writeEventV3(input.coordRoot, pendingEvent, input.writerOptions);
|
|
696
696
|
applyCommittedEvent(state, pendingEvent);
|
|
697
697
|
state.pending = undefined;
|
|
698
|
-
publishProducerState(path, state);
|
|
698
|
+
publishProducerState(input.coordRoot, path, state);
|
|
699
699
|
if (pendingSource && incomingSource === pendingSource) {
|
|
700
700
|
recovered = { state: "recorded", event: pendingEvent, durability, recovered: true };
|
|
701
701
|
}
|
|
@@ -754,7 +754,16 @@ function processHookSignalLocked(
|
|
|
754
754
|
);
|
|
755
755
|
const cursorPromptBootstrap = isCursorPromptBootstrap(input);
|
|
756
756
|
const onboarding = buildMidFlightSessionStart(input, state, rootId, cursorPromptBootstrap);
|
|
757
|
-
|
|
757
|
+
// Onboarding commits eagerly even when the triggering hook defers its
|
|
758
|
+
// drain. The coordination view reads committed history only, so a
|
|
759
|
+
// spooled session.started leaves the session invisible to every
|
|
760
|
+
// authority command (set-task, status --end-turn, heal) until some other
|
|
761
|
+
// writer happens to drain the spool.
|
|
762
|
+
const onboardingInput = withEagerDrain(input);
|
|
763
|
+
commitEventLocked(onboardingInput, state, path, onboarding);
|
|
764
|
+
if (midFlightSignalImpliesOpenTurn(input.signal)) {
|
|
765
|
+
commitMidFlightTurnStart(onboardingInput, state, path, rootId);
|
|
766
|
+
}
|
|
758
767
|
if (!cursorPromptBootstrap) {
|
|
759
768
|
writeProducerDiagnosticV3(input.coordRoot, "mid_flight_onboarding", {
|
|
760
769
|
adapter: input.adapter,
|
|
@@ -881,7 +890,7 @@ function processHookSignalLocked(
|
|
|
881
890
|
span_id: state.current_turn_span?.span_id,
|
|
882
891
|
payload: input.payload,
|
|
883
892
|
});
|
|
884
|
-
publishProducerState(path, state);
|
|
893
|
+
publishProducerState(input.coordRoot, path, state);
|
|
885
894
|
return { state: "ignored" };
|
|
886
895
|
}
|
|
887
896
|
|
|
@@ -907,7 +916,7 @@ function processHookSignalLocked(
|
|
|
907
916
|
? "no_open_turn_span"
|
|
908
917
|
: "ritual_observation_missing",
|
|
909
918
|
});
|
|
910
|
-
publishProducerState(path, state);
|
|
919
|
+
publishProducerState(input.coordRoot, path, state);
|
|
911
920
|
return { state: "ignored" };
|
|
912
921
|
}
|
|
913
922
|
const observation: CursorResponseRitualV3 = {
|
|
@@ -918,7 +927,7 @@ function processHookSignalLocked(
|
|
|
918
927
|
status_box_present_strict: input.turn_ritual.status_box_present_strict,
|
|
919
928
|
};
|
|
920
929
|
state.cursor_response_ritual = observation;
|
|
921
|
-
publishProducerState(path, state);
|
|
930
|
+
publishProducerState(input.coordRoot, path, state);
|
|
922
931
|
writeProducerDiagnosticV3(input.coordRoot, "cursor_response_ritual_observed", {
|
|
923
932
|
instance_id: state.instance_id,
|
|
924
933
|
generation_id: state.generation_id,
|
|
@@ -1805,6 +1814,114 @@ function commitRecoveredToolTurnStart(
|
|
|
1805
1814
|
commitEventLocked(input, state, path, event);
|
|
1806
1815
|
}
|
|
1807
1816
|
|
|
1817
|
+
/**
|
|
1818
|
+
* Signals an adapter can only deliver while a turn is executing. A session
|
|
1819
|
+
* onboarded mid-flight by one of these is provably inside a turn, so the
|
|
1820
|
+
* generation opens with one instead of waiting for the next native prompt.
|
|
1821
|
+
* The native prompt path opens its own turn, session boundaries carry no
|
|
1822
|
+
* turn, and compaction can run between turns.
|
|
1823
|
+
*/
|
|
1824
|
+
function midFlightSignalImpliesOpenTurn(signal: HookSignalV3): boolean {
|
|
1825
|
+
switch (signal) {
|
|
1826
|
+
case "session-start":
|
|
1827
|
+
case "session-end":
|
|
1828
|
+
case "user-prompt-submit":
|
|
1829
|
+
case "pre-compact":
|
|
1830
|
+
case "post-compact":
|
|
1831
|
+
return false;
|
|
1832
|
+
default:
|
|
1833
|
+
return true;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
/** Commit through the append path even when the hook asked to defer its drain. */
|
|
1838
|
+
function withEagerDrain(input: RecordHookSignalV3Input): RecordHookSignalV3Input {
|
|
1839
|
+
if (!input.writerOptions?.deferDrain) return input;
|
|
1840
|
+
return { ...input, writerOptions: { ...input.writerOptions, deferDrain: false } };
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
/**
|
|
1844
|
+
* Open a derived turn for a session onboarded mid-flight (ADR 0078) by a
|
|
1845
|
+
* signal that only an executing turn can deliver. Without it the fresh
|
|
1846
|
+
* generation has no current turn: command telemetry refuses to join
|
|
1847
|
+
* (`turn_not_started`), tool spans nest under the session span, and after an
|
|
1848
|
+
* epoch rotation (ADR 0137) the live session cannot satisfy its end-of-turn
|
|
1849
|
+
* gate until a human submits the next prompt. The payload's native turn id
|
|
1850
|
+
* is kept when the adapter supplies one, so the turn already running keeps
|
|
1851
|
+
* its identity across the boundary and later signals join it directly.
|
|
1852
|
+
*/
|
|
1853
|
+
function commitMidFlightTurnStart(
|
|
1854
|
+
input: RecordHookSignalV3Input,
|
|
1855
|
+
state: HookProducerStateV3,
|
|
1856
|
+
path: string,
|
|
1857
|
+
rootId: `root_${string}`,
|
|
1858
|
+
recovery?: {
|
|
1859
|
+
source_event: string;
|
|
1860
|
+
confidence: "high" | "medium" | "low";
|
|
1861
|
+
attribution_method: "native_payload" | "session_env";
|
|
1862
|
+
},
|
|
1863
|
+
): void {
|
|
1864
|
+
const fingerprintContext = fingerprintContextV3(
|
|
1865
|
+
input.coordRoot,
|
|
1866
|
+
rootId,
|
|
1867
|
+
state.generation_id,
|
|
1868
|
+
state.privacy_epoch_id,
|
|
1869
|
+
);
|
|
1870
|
+
const clock = signalClock(input);
|
|
1871
|
+
const span = openSpanStateV3({
|
|
1872
|
+
span_id: spanIdV3(),
|
|
1873
|
+
parent_span_id: state.session_span.span_id,
|
|
1874
|
+
boot_id: state.boot_id,
|
|
1875
|
+
clock,
|
|
1876
|
+
});
|
|
1877
|
+
state.current_turn_span = span;
|
|
1878
|
+
const nativeTurnId = input.payload.turn_id;
|
|
1879
|
+
state.current_native_turn_id = nativeTurnId;
|
|
1880
|
+
const event = normalizeHookEventV3("user-prompt-submit", recoveryBoundaryPayload(input.payload), {
|
|
1881
|
+
coordRoot: input.coordRoot,
|
|
1882
|
+
adapter: input.adapter,
|
|
1883
|
+
adapterVersion: input.adapterVersion,
|
|
1884
|
+
harnessVersion: input.harnessVersion,
|
|
1885
|
+
root_id: rootId,
|
|
1886
|
+
run_id: input.run_id,
|
|
1887
|
+
workflow_id: input.workflow_id,
|
|
1888
|
+
workflow_agent_id: input.workflow_agent_id,
|
|
1889
|
+
instance_id: state.instance_id,
|
|
1890
|
+
generation_id: state.generation_id,
|
|
1891
|
+
attestation_id: state.attestation_id,
|
|
1892
|
+
producer_id: input.producer_id,
|
|
1893
|
+
boot_id: state.boot_id,
|
|
1894
|
+
sequence: state.next_sequence,
|
|
1895
|
+
build_id: input.build_id,
|
|
1896
|
+
platform: input.platform,
|
|
1897
|
+
bridge: input.bridge,
|
|
1898
|
+
capability_profile: state.capability_profile,
|
|
1899
|
+
fingerprintContext,
|
|
1900
|
+
turn_native_id: nativeTurnId ?? `mid-flight:${state.generation_id}:${state.turn_ordinal + 1}`,
|
|
1901
|
+
span_id: span.span_id,
|
|
1902
|
+
caused_by: state.last_event_id ? [state.last_event_id] : [],
|
|
1903
|
+
observed_at: clock.observed_at,
|
|
1904
|
+
monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
|
|
1905
|
+
clock_id: state.clock_id,
|
|
1906
|
+
});
|
|
1907
|
+
if (event?.event_type !== "turn.started") {
|
|
1908
|
+
throw new Error("mid-flight turn start could not be normalized");
|
|
1909
|
+
}
|
|
1910
|
+
event.provenance = {
|
|
1911
|
+
...event.provenance,
|
|
1912
|
+
source_event: recovery?.source_event ?? `${input.adapter}.recovery`,
|
|
1913
|
+
attestation: "derived",
|
|
1914
|
+
confidence: recovery?.confidence ?? (nativeTurnId ? "medium" : "low"),
|
|
1915
|
+
attribution: {
|
|
1916
|
+
method: recovery?.attribution_method ?? (nativeTurnId ? "native_payload" : "session_env"),
|
|
1917
|
+
state: "verified",
|
|
1918
|
+
subject_instance_id: state.instance_id,
|
|
1919
|
+
},
|
|
1920
|
+
};
|
|
1921
|
+
assertEventV3(event);
|
|
1922
|
+
commitEventLocked(input, state, path, event);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1808
1925
|
/** The single pending-publish/write/apply/publish cycle every locked event commit uses. */
|
|
1809
1926
|
function commitEventLocked(
|
|
1810
1927
|
input: RecordHookSignalV3Input,
|
|
@@ -1816,11 +1933,11 @@ function commitEventLocked(
|
|
|
1816
1933
|
markObservedClockRegressionV3(event, state.last_observed_at);
|
|
1817
1934
|
assertEventV3(event);
|
|
1818
1935
|
state.pending = { ...(sourceId ? { source_id: sourceId } : {}), event };
|
|
1819
|
-
publishProducerState(path, state);
|
|
1936
|
+
publishProducerState(input.coordRoot, path, state);
|
|
1820
1937
|
const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
|
|
1821
1938
|
applyCommittedEvent(state, event);
|
|
1822
1939
|
state.pending = undefined;
|
|
1823
|
-
publishProducerState(path, state);
|
|
1940
|
+
publishProducerState(input.coordRoot, path, state);
|
|
1824
1941
|
return durability;
|
|
1825
1942
|
}
|
|
1826
1943
|
|
|
@@ -1877,7 +1994,7 @@ function commitContextObservation(
|
|
|
1877
1994
|
contextProvenance?.source_witness &&
|
|
1878
1995
|
state.last_context_source_witness === contextProvenance.source_witness
|
|
1879
1996
|
) {
|
|
1880
|
-
publishProducerState(path, state);
|
|
1997
|
+
publishProducerState(input.coordRoot, path, state);
|
|
1881
1998
|
return;
|
|
1882
1999
|
}
|
|
1883
2000
|
if (measurement.state === "observed" && contextProvenance?.source_witness) {
|
|
@@ -2008,13 +2125,13 @@ function maybeCommitActiveRuntimeContextObservation(
|
|
|
2008
2125
|
return;
|
|
2009
2126
|
}
|
|
2010
2127
|
if (input.adapter === "cursor" && state.cursor_mode === "cloud") {
|
|
2011
|
-
publishProducerState(path, state);
|
|
2128
|
+
publishProducerState(input.coordRoot, path, state);
|
|
2012
2129
|
return;
|
|
2013
2130
|
}
|
|
2014
2131
|
|
|
2015
2132
|
const transcriptPath = runtimeTranscriptPath(input, state);
|
|
2016
2133
|
if (!transcriptPath && input.adapter !== "cursor") {
|
|
2017
|
-
publishProducerState(path, state);
|
|
2134
|
+
publishProducerState(input.coordRoot, path, state);
|
|
2018
2135
|
return;
|
|
2019
2136
|
}
|
|
2020
2137
|
const runtime = readRuntimeContextTelemetry(
|
|
@@ -2033,7 +2150,7 @@ function maybeCommitActiveRuntimeContextObservation(
|
|
|
2033
2150
|
);
|
|
2034
2151
|
if (runtime.bytes_read > 0) recordRuntimeTelemetryTiming(state, runtime.io_duration_ms);
|
|
2035
2152
|
if (runtime.state !== "observed") {
|
|
2036
|
-
publishProducerState(path, state);
|
|
2153
|
+
publishProducerState(input.coordRoot, path, state);
|
|
2037
2154
|
return;
|
|
2038
2155
|
}
|
|
2039
2156
|
|
|
@@ -2300,7 +2417,7 @@ function reconcilePendingRuntimeContexts(
|
|
|
2300
2417
|
if (state.pending_runtime_contexts?.length === 0) {
|
|
2301
2418
|
state.pending_runtime_contexts = undefined;
|
|
2302
2419
|
}
|
|
2303
|
-
publishProducerState(path, state);
|
|
2420
|
+
publishProducerState(input.coordRoot, path, state);
|
|
2304
2421
|
}
|
|
2305
2422
|
|
|
2306
2423
|
function reconcilePendingRuntimeContextsBeforeApprovedEnd(
|
|
@@ -3078,7 +3195,7 @@ export function recordApprovedSessionEndV3(
|
|
|
3078
3195
|
const durability = writeEventV3(input.coordRoot, pending, input.writerOptions);
|
|
3079
3196
|
applyCommittedEvent(state, pending);
|
|
3080
3197
|
state.pending = undefined;
|
|
3081
|
-
publishProducerState(record.path, state);
|
|
3198
|
+
publishProducerState(input.coordRoot, record.path, state);
|
|
3082
3199
|
recovered = true;
|
|
3083
3200
|
if (pending.event_type === "session.ended") {
|
|
3084
3201
|
return { state: "recorded", event: pending, durability, recovered };
|
|
@@ -3175,11 +3292,11 @@ export function recordApprovedSessionEndV3(
|
|
|
3175
3292
|
assertEventV3(event);
|
|
3176
3293
|
|
|
3177
3294
|
state.pending = { event };
|
|
3178
|
-
publishProducerState(record.path, state);
|
|
3295
|
+
publishProducerState(input.coordRoot, record.path, state);
|
|
3179
3296
|
const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
|
|
3180
3297
|
applyCommittedEvent(state, event);
|
|
3181
3298
|
state.pending = undefined;
|
|
3182
|
-
publishProducerState(record.path, state);
|
|
3299
|
+
publishProducerState(input.coordRoot, record.path, state);
|
|
3183
3300
|
return { state: "recorded", event, durability, recovered };
|
|
3184
3301
|
} finally {
|
|
3185
3302
|
lease.release();
|
|
@@ -3263,7 +3380,7 @@ export function salvageOpenSpansV3(input: SalvageOpenSpansV3Input): SalvageOpenS
|
|
|
3263
3380
|
writeEventV3(input.coordRoot, pendingEvent, input.writerOptions);
|
|
3264
3381
|
applyCommittedEvent(state, pendingEvent);
|
|
3265
3382
|
state.pending = undefined;
|
|
3266
|
-
publishProducerState(record.path, state);
|
|
3383
|
+
publishProducerState(input.coordRoot, record.path, state);
|
|
3267
3384
|
}
|
|
3268
3385
|
const allowed = new Set(input.allowed_span_ids);
|
|
3269
3386
|
const fingerprintContext = fingerprintContextV3(
|
|
@@ -3345,6 +3462,155 @@ export function listHookProducerStateRecordsV3(
|
|
|
3345
3462
|
);
|
|
3346
3463
|
}
|
|
3347
3464
|
|
|
3465
|
+
export interface ReanchorArchivedHookProducersV3Input {
|
|
3466
|
+
coordRoot: string;
|
|
3467
|
+
archivedEpoch: string;
|
|
3468
|
+
mode: EventV3WriteMode;
|
|
3469
|
+
build_id: `build_${string}`;
|
|
3470
|
+
platform: "linux" | "windows" | "macos" | "unknown";
|
|
3471
|
+
observed_at?: string;
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
export interface ReanchorArchivedHookProducersV3Result {
|
|
3475
|
+
sessions: number;
|
|
3476
|
+
turns: number;
|
|
3477
|
+
}
|
|
3478
|
+
|
|
3479
|
+
/**
|
|
3480
|
+
* Re-anchor every live archived hook producer into a newly activated epoch.
|
|
3481
|
+
*
|
|
3482
|
+
* The archive rename is the rotation's atomic snapshot: it includes every
|
|
3483
|
+
* state published before the boundary without copying any old boot sequence
|
|
3484
|
+
* or causal link into the successor. Each live session gets a fresh
|
|
3485
|
+
* generation and attestation. Only a turn that was open at the boundary is
|
|
3486
|
+
* reopened, which makes command telemetry joinable before another adapter
|
|
3487
|
+
* hook fires while preserving the between-turn state for idle sessions.
|
|
3488
|
+
*/
|
|
3489
|
+
export function reanchorArchivedHookProducersV3(
|
|
3490
|
+
input: ReanchorArchivedHookProducersV3Input,
|
|
3491
|
+
): ReanchorArchivedHookProducersV3Result {
|
|
3492
|
+
const control = readEventV3ControlState(input.coordRoot);
|
|
3493
|
+
if (control.state !== input.mode) {
|
|
3494
|
+
throw new Error(`event_v3_rotation_reanchor_gate_closed:${control.state}`);
|
|
3495
|
+
}
|
|
3496
|
+
const rootId = control.genesis.event.scope.root_id as `root_${string}`;
|
|
3497
|
+
const epochId = control.genesis.profile.privacy_key_epoch;
|
|
3498
|
+
const genesisId = control.genesis.event.payload.genesis_id as `gex_${string}`;
|
|
3499
|
+
const boundaryEventId =
|
|
3500
|
+
control.state === "candidate"
|
|
3501
|
+
? control.genesis.event.event_id
|
|
3502
|
+
: control.activation.event.event_id;
|
|
3503
|
+
const observedAt = input.observed_at ?? new Date().toISOString();
|
|
3504
|
+
let sessions = 0;
|
|
3505
|
+
let turns = 0;
|
|
3506
|
+
|
|
3507
|
+
for (const archived of listArchivedHookProducerStatesV3(input.archivedEpoch)) {
|
|
3508
|
+
const previous = archived.state;
|
|
3509
|
+
if (previous.terminal || !previous.started_event_id) continue;
|
|
3510
|
+
const path = producerStatePath(input.coordRoot, previous.adapter, archived.sessionHash);
|
|
3511
|
+
const lease = acquireStateLeaseWithRetry(input.coordRoot, path, 40);
|
|
3512
|
+
if (!lease) {
|
|
3513
|
+
throw new Error(`event_v3_rotation_reanchor_busy:${previous.instance_id}`);
|
|
3514
|
+
}
|
|
3515
|
+
try {
|
|
3516
|
+
if (existsSync(path)) {
|
|
3517
|
+
const current = readProducerState(path);
|
|
3518
|
+
if (current.epoch_genesis_id === genesisId) continue;
|
|
3519
|
+
}
|
|
3520
|
+
const sourceEvent = `${previous.adapter}.epoch-rotation-reanchor`;
|
|
3521
|
+
const rotationInput: RecordHookSignalV3Input = {
|
|
3522
|
+
coordRoot: input.coordRoot,
|
|
3523
|
+
mode: input.mode,
|
|
3524
|
+
signal: "session-start",
|
|
3525
|
+
payload: {
|
|
3526
|
+
raw: {},
|
|
3527
|
+
...(previous.current_native_turn_id ? { turn_id: previous.current_native_turn_id } : {}),
|
|
3528
|
+
...(previous.adapter === "cursor" && previous.cursor_mode
|
|
3529
|
+
? { cursor_mode: previous.cursor_mode }
|
|
3530
|
+
: {}),
|
|
3531
|
+
},
|
|
3532
|
+
adapter: previous.adapter,
|
|
3533
|
+
instance_id: previous.instance_id,
|
|
3534
|
+
producer_id: "prd_agent-hook",
|
|
3535
|
+
build_id: input.build_id,
|
|
3536
|
+
platform: input.platform,
|
|
3537
|
+
observed_at: observedAt,
|
|
3538
|
+
writerOptions: { expectedGenesisId: genesisId },
|
|
3539
|
+
};
|
|
3540
|
+
const state = newProducerState(
|
|
3541
|
+
rotationInput,
|
|
3542
|
+
previous.session_id,
|
|
3543
|
+
epochId,
|
|
3544
|
+
boundaryEventId as `evt_${string}`,
|
|
3545
|
+
genesisId,
|
|
3546
|
+
);
|
|
3547
|
+
const sessionStart = buildMidFlightSessionStart(rotationInput, state, rootId);
|
|
3548
|
+
if (sessionStart.event_type !== "session.started") {
|
|
3549
|
+
throw new Error("rotation re-anchor session start could not be normalized");
|
|
3550
|
+
}
|
|
3551
|
+
sessionStart.provenance = {
|
|
3552
|
+
...sessionStart.provenance,
|
|
3553
|
+
source_event: sourceEvent,
|
|
3554
|
+
attestation: "derived",
|
|
3555
|
+
confidence: "high",
|
|
3556
|
+
attribution: {
|
|
3557
|
+
method: "session_env",
|
|
3558
|
+
state: "verified",
|
|
3559
|
+
subject_instance_id: state.instance_id,
|
|
3560
|
+
},
|
|
3561
|
+
};
|
|
3562
|
+
sessionStart.payload.resume = {
|
|
3563
|
+
state: "unknown",
|
|
3564
|
+
reason: "epoch_rotation_reanchor",
|
|
3565
|
+
};
|
|
3566
|
+
assertEventV3(sessionStart);
|
|
3567
|
+
commitEventLocked(rotationInput, state, path, sessionStart);
|
|
3568
|
+
sessions += 1;
|
|
3569
|
+
|
|
3570
|
+
if (previous.current_turn_id && previous.current_turn_span) {
|
|
3571
|
+
commitMidFlightTurnStart(rotationInput, state, path, rootId, {
|
|
3572
|
+
source_event: sourceEvent,
|
|
3573
|
+
confidence: "high",
|
|
3574
|
+
attribution_method: "session_env",
|
|
3575
|
+
});
|
|
3576
|
+
turns += 1;
|
|
3577
|
+
}
|
|
3578
|
+
} finally {
|
|
3579
|
+
lease.release();
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
return { sessions, turns };
|
|
3583
|
+
}
|
|
3584
|
+
|
|
3585
|
+
function listArchivedHookProducerStatesV3(
|
|
3586
|
+
archivedEpoch: string,
|
|
3587
|
+
): Array<{ sessionHash: `hid_${string}`; state: HookProducerStateV3 }> {
|
|
3588
|
+
const producerRoot = join(resolve(archivedEpoch), "private-producers");
|
|
3589
|
+
if (!existsSync(producerRoot)) return [];
|
|
3590
|
+
const records: Array<{ sessionHash: `hid_${string}`; state: HookProducerStateV3 }> = [];
|
|
3591
|
+
for (const adapter of ["claude-code", "codex", "cursor"] as const) {
|
|
3592
|
+
const directory = join(producerRoot, adapter);
|
|
3593
|
+
if (!existsSync(directory)) continue;
|
|
3594
|
+
const metadata = lstatSync(directory);
|
|
3595
|
+
if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
|
|
3596
|
+
throw new Error("V3 archived producer state directory is unsafe");
|
|
3597
|
+
}
|
|
3598
|
+
for (const name of readdirSync(directory).filter((entry) =>
|
|
3599
|
+
/^hid_[a-f0-9]{64}\.json$/.test(entry),
|
|
3600
|
+
)) {
|
|
3601
|
+
records.push({
|
|
3602
|
+
sessionHash: name.slice(0, -".json".length) as `hid_${string}`,
|
|
3603
|
+
state: readProducerState(join(directory, name)),
|
|
3604
|
+
});
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
return records.sort(
|
|
3608
|
+
(left, right) =>
|
|
3609
|
+
left.state.generation_id.localeCompare(right.state.generation_id) ||
|
|
3610
|
+
left.sessionHash.localeCompare(right.sessionHash),
|
|
3611
|
+
);
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3348
3614
|
function hookSignalCapability(signal: HookSignalV3): AdapterSignalV3 {
|
|
3349
3615
|
switch (signal) {
|
|
3350
3616
|
case "session-start":
|
|
@@ -3923,7 +4189,12 @@ function acquireStateLease(coordRoot: string, statePath: string) {
|
|
|
3923
4189
|
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
3924
4190
|
chmodSync(producerRoot, 0o700);
|
|
3925
4191
|
chmodSync(directory, 0o700);
|
|
3926
|
-
|
|
4192
|
+
const acquiredControl = readEventV3ControlState(coordRoot);
|
|
4193
|
+
const acquiredGenesis =
|
|
4194
|
+
acquiredControl.state === "candidate" || acquiredControl.state === "active"
|
|
4195
|
+
? acquiredControl.genesis.event.payload.genesis_id
|
|
4196
|
+
: undefined;
|
|
4197
|
+
const lease = acquireNoClobberLease({
|
|
3927
4198
|
path: `${statePath}.lease`,
|
|
3928
4199
|
scope: "event-v3-hook-producer",
|
|
3929
4200
|
authoritySha256: createHash("sha256")
|
|
@@ -3934,9 +4205,32 @@ function acquireStateLease(coordRoot: string, statePath: string) {
|
|
|
3934
4205
|
staleAfterMs: 5_000,
|
|
3935
4206
|
validateStaleOwner: (owner) => owner.host === hostname() && !pidIsAlive(owner.pid),
|
|
3936
4207
|
});
|
|
4208
|
+
return {
|
|
4209
|
+
...lease,
|
|
4210
|
+
release() {
|
|
4211
|
+
try {
|
|
4212
|
+
lease.release();
|
|
4213
|
+
} catch (error) {
|
|
4214
|
+
const current = readEventV3ControlState(coordRoot);
|
|
4215
|
+
const epochReplaced =
|
|
4216
|
+
acquiredGenesis !== undefined &&
|
|
4217
|
+
(current.state === "candidate" || current.state === "active") &&
|
|
4218
|
+
current.genesis.event.payload.genesis_id !== acquiredGenesis;
|
|
4219
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT" && epochReplaced) return;
|
|
4220
|
+
throw error;
|
|
4221
|
+
}
|
|
4222
|
+
},
|
|
4223
|
+
};
|
|
3937
4224
|
}
|
|
3938
4225
|
|
|
3939
|
-
function publishProducerState(path: string, state: HookProducerStateV3): void {
|
|
4226
|
+
function publishProducerState(coordRoot: string, path: string, state: HookProducerStateV3): void {
|
|
4227
|
+
const control = readEventV3ControlState(coordRoot);
|
|
4228
|
+
if (
|
|
4229
|
+
(control.state === "candidate" || control.state === "active") &&
|
|
4230
|
+
state.epoch_genesis_id !== control.genesis.event.payload.genesis_id
|
|
4231
|
+
) {
|
|
4232
|
+
return;
|
|
4233
|
+
}
|
|
3940
4234
|
const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
|
|
3941
4235
|
let fd: number | undefined;
|
|
3942
4236
|
try {
|