@sema-agent/core 7.6.0 → 7.6.2

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +2 -2
  3. package/dist/agents/cascade.d.ts +2 -3
  4. package/dist/agents/repair-loop.d.ts +2 -2
  5. package/dist/agents/retain-ledger.d.ts +2 -3
  6. package/dist/agents/send-message-tool.d.ts +2 -2
  7. package/dist/agents/session-util.d.ts +2 -2
  8. package/dist/agents/subagent.d.ts +3 -4
  9. package/dist/agents/teacher.d.ts +2 -2
  10. package/dist/agents/team.d.ts +2 -2
  11. package/dist/agents/verify.d.ts +5 -6
  12. package/dist/core/agent-definition.d.ts +172 -0
  13. package/dist/core/agent-definition.js +1 -0
  14. package/dist/core/checkpoint-store.d.ts +8 -4
  15. package/dist/core/delegation-frames.d.ts +298 -0
  16. package/dist/core/delegation-frames.js +21 -0
  17. package/dist/core/engine-notice.d.ts +555 -0
  18. package/dist/core/engine-notice.js +55 -0
  19. package/dist/core/gate-fold.d.ts +12 -0
  20. package/dist/core/gate-fold.js +158 -0
  21. package/dist/core/gate-lanes.d.ts +93 -0
  22. package/dist/core/gate-lanes.js +626 -0
  23. package/dist/core/hands-band.d.ts +134 -0
  24. package/dist/core/hands-band.js +1 -0
  25. package/dist/core/hooks.d.ts +20 -101
  26. package/dist/core/hooks.js +53 -854
  27. package/dist/core/mcp-failure.d.ts +43 -5
  28. package/dist/core/mcp-failure.js +31 -14
  29. package/dist/core/mcp-server-spec.d.ts +217 -0
  30. package/dist/core/mcp-server-spec.js +1 -0
  31. package/dist/core/model-seat.d.ts +99 -0
  32. package/dist/core/model-seat.js +1 -0
  33. package/dist/core/reminder-mint.d.ts +10 -0
  34. package/dist/core/reminder-mint.js +3 -0
  35. package/dist/core/runner/contracts.d.ts +382 -6
  36. package/dist/core/runner/gate-exit.d.ts +177 -9
  37. package/dist/core/runner/gate-exit.js +70 -1
  38. package/dist/core/runner/prepare-caps-and-workflow.d.ts +2 -7
  39. package/dist/core/runner/prepare-delegation-surface.d.ts +2 -7
  40. package/dist/core/runner/prepare-run-refs.d.ts +12 -0
  41. package/dist/core/runner/prepare-run-refs.js +5 -0
  42. package/dist/core/runner/prepare-task.d.ts +2 -2
  43. package/dist/core/runner/runtask.d.ts +4 -71
  44. package/dist/core/runner/runtask.js +18 -6
  45. package/dist/core/runner-deps.d.ts +1416 -0
  46. package/dist/core/runner-deps.js +1 -0
  47. package/dist/core/runtime-caps.d.ts +164 -0
  48. package/dist/core/runtime-caps.js +1 -0
  49. package/dist/core/task-event.d.ts +910 -0
  50. package/dist/core/task-event.js +1 -0
  51. package/dist/core/task-limits.d.ts +110 -0
  52. package/dist/core/task-limits.js +1 -0
  53. package/dist/core/task-result.d.ts +809 -0
  54. package/dist/core/task-result.js +1 -0
  55. package/dist/core/task-spec.d.ts +1370 -0
  56. package/dist/core/task-spec.js +1 -0
  57. package/dist/core/task-stream.d.ts +382 -0
  58. package/dist/core/task-stream.js +1 -0
  59. package/dist/core/tool-spec.d.ts +1174 -0
  60. package/dist/core/tool-spec.js +1 -0
  61. package/dist/core/types.d.ts +26 -7691
  62. package/dist/core/types.js +2 -76
  63. package/dist/core/warm-resume.d.ts +2 -2
  64. package/dist/index.d.ts +2 -1
  65. package/dist/index.js +1 -1
  66. package/dist/orchestration/goal.d.ts +2 -2
  67. package/dist/orchestration/run-spec.d.ts +2 -2
  68. package/dist/orchestration/run-workflow-tool.d.ts +3 -3
  69. package/dist/orchestration/workflow.d.ts +4 -4
  70. package/dist/scenarios/scenario-registry.d.ts +3 -3
  71. package/dist/scenarios/teacher-quickstart.d.ts +2 -2
  72. package/dist/server/http.d.ts +2 -2
  73. package/dist/stores/file/fs-atomic.d.ts +88 -12
  74. package/dist/stores/file/fs-atomic.js +184 -55
  75. package/dist/stores/file/index.d.ts +1 -0
  76. package/dist/stores/file/index.js +1 -0
  77. package/package.json +1 -1
  78. package/test/export-surface.snapshot.json +9 -1
@@ -1,76 +1,2 @@
1
- import { observeThenableRejection } from "./safe-notify.js";
2
- let malformedDelegationSeatAnnounced = false;
3
- export function __resetMalformedDelegationSeatAnnouncement() {
4
- malformedDelegationSeatAnnounced = false;
5
- }
6
- export function deliverDelegationLifecycle(seat, event, notifier, site) {
7
- if (typeof seat === "function") {
8
- const frame = Object.freeze(Object.assign(Object.create(null), event));
9
- notifier.notify(() => observeThenableRejection(seat(frame), notifier, site), site);
10
- return;
11
- }
12
- if (seat !== undefined && !malformedDelegationSeatAnnounced) {
13
- malformedDelegationSeatAnnounced = true;
14
- try {
15
- console.warn(`The delegation-lifecycle sink (RunnerDeps.onDelegationLifecycle) holds ${seat === null ? "null" : typeof seat} — not a ` +
16
- `function. Delegation spawn/terminal frames are NOT delivered until the wiring is fixed (omit the key, or wire a function).`);
17
- }
18
- catch {
19
- }
20
- }
21
- }
22
- let malformedNoticeSeatAnnounced = false;
23
- export function __resetMalformedNoticeSeatAnnouncement() {
24
- malformedNoticeSeatAnnounced = false;
25
- }
26
- export function deliverEngineNotice(onNotice, notice) {
27
- if (notice.sessionId === undefined && typeof notice.detail?.["sessionId"] === "string") {
28
- notice = { ...notice, sessionId: notice.detail["sessionId"] };
29
- }
30
- if (typeof onNotice === "function") {
31
- try {
32
- const r = onNotice(notice);
33
- if (typeof r?.then === "function") {
34
- r.then(undefined, () => {
35
- });
36
- }
37
- }
38
- catch {
39
- }
40
- return;
41
- }
42
- if (onNotice !== undefined) {
43
- if (!malformedNoticeSeatAnnounced) {
44
- malformedNoticeSeatAnnounced = true;
45
- try {
46
- console.warn(`The structured notice sink (RunnerDeps.onNotice) holds ${onNotice === null ? "null" : typeof onNotice} — not a ` +
47
- `function. Notices fall back to console.warn until the wiring is fixed (omit the key, or wire a function).`);
48
- }
49
- catch {
50
- }
51
- }
52
- }
53
- console.warn(notice.message);
54
- }
55
- export function undrainedUserInputNotices(counts, taskId, sessionId, runId) {
56
- const tid = taskId !== undefined ? { taskId } : {};
57
- const sid = sessionId !== undefined ? { sessionId } : {};
58
- const rid = runId !== undefined ? { runId } : {};
59
- const tail = `accepted as "queued" were never consumed — the run ended first. They are NOT redelivered; re-send against a live run if still wanted.`;
60
- const out = [];
61
- if (counts.steer > 0) {
62
- out.push({
63
- code: "task.user_steer_undrained",
64
- message: `${counts.steer} user steer(s) ${tail}`,
65
- detail: { steer: counts.steer, ...tid, ...sid, ...rid },
66
- });
67
- }
68
- if (counts.followUp > 0) {
69
- out.push({
70
- code: "task.user_followup_undrained",
71
- message: `${counts.followUp} user follow-up(s) ${tail}`,
72
- detail: { followUp: counts.followUp, ...tid, ...sid, ...rid },
73
- });
74
- }
75
- return out;
76
- }
1
+ export { __resetMalformedDelegationSeatAnnouncement, deliverDelegationLifecycle } from "./delegation-frames.js";
2
+ export { __resetMalformedNoticeSeatAnnouncement, deliverEngineNotice, undrainedUserInputNotices } from "./engine-notice.js";
@@ -1,4 +1,4 @@
1
- import type { Runner } from "./runner/runtask.js";
1
+ import type { RunnerSelfSeat } from "./runner/contracts.js";
2
2
  import type { TaskResult, TaskSpec } from "./types.js";
3
3
  /**
4
4
  * design/114 P0 — a convenience for the RECOMMENDED safe warm-resume of a (failed / timed-out) run: FORK the
@@ -17,6 +17,6 @@ import type { TaskResult, TaskSpec } from "./types.js";
17
17
  * the directive for picking up the work; pass the model (and any other spec fields) via `opts` — a continuation
18
18
  * still needs a model like any task.
19
19
  */
20
- export declare function warmResume(runner: Runner, sessionId: string, objective: string, opts?: Omit<Partial<TaskSpec>, "objective" | "sessionId"> & {
20
+ export declare function warmResume(runner: RunnerSelfSeat, sessionId: string, objective: string, opts?: Omit<Partial<TaskSpec>, "objective" | "sessionId"> & {
21
21
  owner?: string | null;
22
22
  }): Promise<TaskResult | null>;
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  export { Runner, runTask } from "./core/runner/runtask.js";
9
9
  export { resolveTaskLimits } from "./core/runner/prepare-task.js";
10
10
  export type { RunInternals, ResolvedWorkspace } from "./core/runner/prepare-task.js";
11
+ export type { RunnerSelfSeat } from "./core/runner/contracts.js";
11
12
  export type { BudgetAxis } from "./core/runner/assemble-result.js";
12
13
  export { terminalProjection } from "./core/runner/terminal-projection.js";
13
14
  export { SESSION_LOG_DIGEST_SCHEME, sessionEntryDigest, sessionLogDigest, sessionLogDigestsComparable, } from "./engine/session/log-digest.js";
@@ -113,7 +114,7 @@ export type { FileHistoryStore, FileHistoryResult, FileHistoryTrackResult, FileH
113
114
  export type { InMemoryFileHistoryStoreOptions } from "./core/file-history-store.js";
114
115
  export { DEFAULT_FILE_HISTORY_BOUNDARY_KEEP, resolveFileHistoryRetention, fileHistoryBoundariesToKeep } from "./core/file-history-retention.js";
115
116
  export type { FileHistoryRetentionPolicy } from "./core/file-history-retention.js";
116
- export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileStrategyStore, FileFileHistoryStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, FileStoreLockError, type FileStoreLockErrorCode, type FileStorageBackendOptions, type FileStorageCorruptReadInfo, type FileStrategyStoreOptions, type FileFileHistoryStoreOptions, type FileSessionRepoOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
117
+ export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileStrategyStore, FileFileHistoryStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, FileStoreLockError, type FileStoreLockErrorCode, processFingerprint, isProcessLive, type ProcessFingerprint, type FileStorageBackendOptions, type FileStorageCorruptReadInfo, type FileStrategyStoreOptions, type FileFileHistoryStoreOptions, type FileSessionRepoOptions, type FileCheckpointStoreOptions, } from "./stores/file/index.js";
117
118
  export { CacheBreakDetector, type CacheBreakFinding, type ToolFingerprintInput } from "./core/cache-break-detector.js";
118
119
  export { maybeCompact, type MaybeCompactOptions, type CompactionWindowSafetyInfo } from "./core/auto-compaction.js";
119
120
  export { brainToRuntime } from "./core/runtime.js";
package/dist/index.js CHANGED
@@ -90,7 +90,7 @@ export { worldStillMatchesMint } from "./core/file-history-store.js";
90
90
  export { trackKeyOf as fileHistoryTrackKeyOf, resolveTrackKey as fileHistoryResolveTrackKey } from "./core/file-history-store.js";
91
91
  export { describeTrackKey as fileHistoryDescribeTrackKey, trackKeyFamilyOf as fileHistoryKeyFamilyOf, fileHistoryExportDigest, validateFileHistoryExport } from "./core/file-history-store.js";
92
92
  export { DEFAULT_FILE_HISTORY_BOUNDARY_KEEP, resolveFileHistoryRetention, fileHistoryBoundariesToKeep } from "./core/file-history-retention.js";
93
- export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileStrategyStore, FileFileHistoryStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, FileStoreLockError, } from "./stores/file/index.js";
93
+ export { FileStorageBackend, FileSessionRepo, FileCheckpointStore, FileMemoryStore, FileToolResultStore, FileSessionPolicyStore, FileStrategyStore, FileFileHistoryStore, FileWorkflowJournalStore, MAX_JOURNAL_RESULT_BYTES, oversizeJournalResult, resolveDataRoot, sanitizeScope, sanitizePathComponent, createFileConsolidationLock, atomicWriteFile, writeThenLink, ensureDir, readJsonlRecords, AppendLog, FileStoreLockError, processFingerprint, isProcessLive, } from "./stores/file/index.js";
94
94
  export { CacheBreakDetector } from "./core/cache-break-detector.js";
95
95
  export { maybeCompact } from "./core/auto-compaction.js";
96
96
  export { brainToRuntime } from "./core/runtime.js";
@@ -11,7 +11,7 @@
11
11
  * **无任何 commit/accept 副作用**(纯 loop,同 runRepairLoop SAFE-tier 永不 `fixed`)。要 accept,caller 自走
12
12
  * design/77 Gate-1 out-of-process oracle 隔离边界,不在 goal helper 开后门。
13
13
  */
14
- import type { Runner } from "../core/runner/runtask.js";
14
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
15
15
  import type { CheckpointToken } from "../core/checkpoint-store.js";
16
16
  import type { TaskResult, TaskSpec } from "../core/types.js";
17
17
  /** Read-only snapshot a `doneCheck` sees after one iteration. */
@@ -103,4 +103,4 @@ export declare const DECLARE_DONE_TOOL_NAME = "DeclareDone";
103
103
  * doneCheck feeds its reason back (fenced) and continues. Bounded by maxIterations / budget / wall-clock /
104
104
  * cancel; a durable pause (suspended/needs_review) is a hard boundary that surfaces the checkpointToken.
105
105
  */
106
- export declare function runGoal(runner: Runner, spec: GoalSpec): Promise<GoalResult>;
106
+ export declare function runGoal(runner: RunnerSelfSeat, spec: GoalSpec): Promise<GoalResult>;
@@ -25,7 +25,7 @@
25
25
  * 生命周期(调用方保证起点是干净树 —— preflight 红才有意义)、模型/Runner 装配、chaos 包装。frozen 写入
26
26
  * 面:policy 只挡 Write/Edit 族 = belt;bash 写入由快照恢复兜(suspenders);危险 bash deny 仍是使用方纪律。
27
27
  */
28
- import type { Runner } from "../core/runner/runtask.js";
28
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
29
29
  import type { TaskResult, TaskSpec } from "../core/types.js";
30
30
  import type { TaskOutcome } from "../core/task-outcome.js";
31
31
  import { type OracleBaseline, type OracleGateResult, type OracleVerdict, type SpecContract } from "../core/spec-contract.js";
@@ -59,4 +59,4 @@ export interface RunSpecResult {
59
59
  * `spec.frozen_path_invalid`, `spec.frozen_missing`, `spec.spec_unreadable`, `spec.not_red`,
60
60
  * `spec.oracle_error`.
61
61
  */
62
- export declare function runSpec(runner: Pick<Runner, "runTask"> & Partial<Pick<Runner, "emitTaskOutcome" | "gateBaseline">>, contract: SpecContract, opts: RunSpecOptions): Promise<RunSpecResult>;
62
+ export declare function runSpec(runner: Pick<RunnerSelfSeat, "runTask"> & Partial<Pick<RunnerSelfSeat, "emitTaskOutcome" | "gateBaseline">>, contract: SpecContract, opts: RunSpecOptions): Promise<RunSpecResult>;
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import type { AgentTool } from "../internal/harness-types.js";
13
13
  import type { Model } from "../internal/llm.js";
14
- import type { Runner } from "../core/runner/runtask.js";
14
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
15
15
  import type { WorkflowGovernanceBaseline } from "../core/types.js";
16
16
  import type { WorkflowRunStore } from "../core/workflow-run-store.js";
17
17
  import type { WorkflowJournalStore } from "../core/workflow-journal-store.js";
@@ -128,7 +128,7 @@ export declare function workflowWhenToUseText(m: {
128
128
  export declare function renderNamedWorkflowListing(entries: ReadonlyArray<NamedWorkflowListing>): string | undefined;
129
129
  export interface RunWorkflowToolDeps {
130
130
  /** The Runner that executes child tasks (the workflow's `ctx.agent` → `runner.runTask`). */
131
- runner: Runner;
131
+ runner: RunnerSelfSeat;
132
132
  /** The HARD sandbox (asserted `safeForUntrustedScripts:true`). */
133
133
  scriptRunner: WorkflowScriptRunner;
134
134
  /** Deployment-trusted governance every spawned agent inherits (design/98 §2.5). */
@@ -322,7 +322,7 @@ export interface RunWorkflowToolDeps {
322
322
  * trusted internals — workflow children inherit the host's evaluated gate exactly like a
323
323
  * `createSubagentTool` delegation. (A dep for the same reason as `forwardEvent`: the direct mount's
324
324
  * execute ctx is minimal; `ctx.inheritedGateForChildren` still wins when a wrapping path provides it.) */
325
- inheritedGateForChildren?: () => import("../core/runner/prepare-task.js").InheritedGate;
325
+ inheritedGateForChildren?: () => import("../core/runner/contracts.js").InheritedGate;
326
326
  /** RB-201 FO-3 — the HOST session's auto-mode classifier, threaded via `RunWorkflowOptions.autoModeReview`
327
327
  * so every child this workflow spawns is reviewed before it starts, exactly like a `createSubagentTool`
328
328
  * delegation's child. (A dep for the same reason as `forwardEvent`/`inheritedGateForChildren`: the direct
@@ -1,5 +1,5 @@
1
1
  import type { TSchema } from "typebox";
2
- import type { Runner } from "../core/runner/runtask.js";
2
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
3
3
  import type { AgentDefinition, TaskEvent, TaskResult, TaskSpec } from "../core/types.js";
4
4
  import type { WorkflowRunStore } from "../core/workflow-run-store.js";
5
5
  import type { WorkflowJournalStore, ResumeClaimArgs } from "../core/workflow-journal-store.js";
@@ -452,7 +452,7 @@ export interface RunWorkflowOptions {
452
452
  * (`RunInternals.inheritedGate`) on both spawn legs (`agent()` and `agentStream()`). Set by the
453
453
  * `run_workflow` tool wiring; a directly-started workflow may pass its own. Absent ⇒ children run
454
454
  * under only their own gates (pre-seam behavior). TRUSTED: never a script/model argument. */
455
- inheritedGate?: import("../core/runner/prepare-task.js").InheritedGate;
455
+ inheritedGate?: import("../core/runner/contracts.js").InheritedGate;
456
456
  /** RB-201 FO-3 (workflow lane) — the session's auto-mode classifier, consulted BEFORE each script-spawned
457
457
  * child starts (objective + toolset + output schema, finalized). The delegation-tool lane reviews its
458
458
  * children at one shared point; a workflow's children reach `runner.runTask` through this module instead,
@@ -553,11 +553,11 @@ export declare const workflowResumeClaimFallback: {
553
553
  * `done` rejects. `cancel(reason)` aborts the whole run. A nesting/validation violation throws SYNCHRONOUSLY
554
554
  * from this call (before any handle exists). Thin composition over `runner.runTask` — no Runner changes.
555
555
  */
556
- export declare function startWorkflow<T>(runner: Runner, fn: (ctx: WorkflowRunContext) => Promise<T>, opts?: RunWorkflowOptions, internals?: WorkflowInternals): WorkflowHandle<T>;
556
+ export declare function startWorkflow<T>(runner: RunnerSelfSeat, fn: (ctx: WorkflowRunContext) => Promise<T>, opts?: RunWorkflowOptions, internals?: WorkflowInternals): WorkflowHandle<T>;
557
557
  /**
558
558
  * Run a workflow to completion (design/97 S1a) — the thin await over {@link startWorkflow}: returns the
559
559
  * script's value + the assembled {@link WorkflowRun}. A synchronous nesting/validation throw from
560
560
  * `startWorkflow` becomes a rejected promise here (this function is `async`), preserving the original
561
561
  * `runWorkflow` contract (`await runWorkflow(...)` rejects rather than throwing synchronously).
562
562
  */
563
- export declare function runWorkflow<T>(runner: Runner, fn: (ctx: WorkflowRunContext) => Promise<T>, opts?: RunWorkflowOptions, internals?: WorkflowInternals): Promise<RunWorkflowResult<T>>;
563
+ export declare function runWorkflow<T>(runner: RunnerSelfSeat, fn: (ctx: WorkflowRunContext) => Promise<T>, opts?: RunWorkflowOptions, internals?: WorkflowInternals): Promise<RunWorkflowResult<T>>;
@@ -11,7 +11,7 @@
11
11
  * yet implemented). The model pairing (who's cheap / who's strong / heterogeneous decorrelation) is
12
12
  * deploy-side knowledge — core fills guard-number defaults + prompts + orchestrator choice, never the model.
13
13
  */
14
- import type { Runner } from "../core/runner/runtask.js";
14
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
15
15
  import type { ModelRef, ModelRole, TaskSpec } from "../core/types.js";
16
16
  import { type TeamResult } from "../agents/team.js";
17
17
  import { type VerificationResult } from "../agents/verify.js";
@@ -59,7 +59,7 @@ export interface ScenarioProfile {
59
59
  export declare const SCENARIO_REGISTRY: Record<ScenarioId, ScenarioProfile>;
60
60
  export interface RunScenarioOptions {
61
61
  scenario: ScenarioId;
62
- runner: Runner;
62
+ runner: RunnerSelfSeat;
63
63
  /** What the scenario operates on — the team `topic` (design/code review) or the solo task objective. */
64
64
  objective: string;
65
65
  /**
@@ -83,7 +83,7 @@ export interface RunScenarioOptions {
83
83
  /** Per-scenario result union — each entry returns its orchestrator's native result shape. */
84
84
  export type RunScenarioResult = {
85
85
  scenario: "solo";
86
- result: Awaited<ReturnType<Runner["runTask"]>>;
86
+ result: Awaited<ReturnType<RunnerSelfSeat["runTask"]>>;
87
87
  } | {
88
88
  scenario: "design-review";
89
89
  result: TeamResult;
@@ -10,7 +10,7 @@
10
10
  * defaults only. The guard defaults are NOT re-stated here: we let teacher.ts own them (single source of
11
11
  * truth, no drift).
12
12
  */
13
- import type { Runner } from "../core/runner/runtask.js";
13
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
14
14
  import type { ModelRef, TaskSpec } from "../core/types.js";
15
15
  import { type TeacherRunResult } from "../agents/teacher.js";
16
16
  export interface TeacherModePair {
@@ -25,7 +25,7 @@ export interface TeacherModePair {
25
25
  * student task spec's `model`. All guards use teacher.ts's built-in defaults (not re-stated here, so core
26
26
  * owns the single source of truth). The caller supplies only the two models + the task spec.
27
27
  */
28
- export declare function teacherMode(runner: Runner, spec: TaskSpec, pair: TeacherModePair): Promise<TeacherRunResult>;
28
+ export declare function teacherMode(runner: RunnerSelfSeat, spec: TaskSpec, pair: TeacherModePair): Promise<TeacherRunResult>;
29
29
  /**
30
30
  * Teacher-mode preset profile (G2: a profile = which two models to fill). The two model cells are role-name
31
31
  * placeholders the deploy side maps to concrete models (core never guesses); guard defaults are NOT pinned
@@ -1,6 +1,6 @@
1
1
  import http from "node:http";
2
2
  import type { IncomingMessage } from "node:http";
3
- import type { Runner } from "../core/runner/runtask.js";
3
+ import type { RunnerSelfSeat } from "../core/runner/contracts.js";
4
4
  import type { ImageInput, TaskSpec } from "../core/types.js";
5
5
  /** Inbound request body. Tool *code* can never come over the wire — the server injects tools via resolveSpec. */
6
6
  export interface TaskRequestBody {
@@ -10,7 +10,7 @@ export interface TaskRequestBody {
10
10
  [key: string]: unknown;
11
11
  }
12
12
  export interface TaskServerOptions {
13
- runner: Runner;
13
+ runner: RunnerSelfSeat;
14
14
  /**
15
15
  * Map an inbound request body to a full TaskSpec. This is where the SERVER decides the model,
16
16
  * tools, MCP servers, system prompt, and limits for the request. The body only supplies content
@@ -184,10 +184,11 @@ export declare class FileStoreLockError extends Error {
184
184
  ownerPid?: number | undefined);
185
185
  }
186
186
  /**
187
- * The coarse boot guard (§2.4): an `O_EXCL` PID file (`root/LOCK` for the whole data dir; one per store
187
+ * The coarse boot guard (§2.4): an `O_EXCL` owner file (`root/LOCK` for the whole data dir; one per store
188
188
  * ledger directory via {@link acquireStoreDirLock}) that forbids two processes sharing it. A second
189
- * instance fails fast ("another instance owns this data dir"). A STALE lock (the writing PID is dead) is
190
- * pruned and re-acquired `proper-lockfile`/CC `concurrentSessions` parity.
189
+ * instance fails fast ("another instance owns this data dir"). A STALE lock (the process that WROTE it is
190
+ * gone see {@link isProcessLive}, which is not the same question as "is that pid free") is pruned and
191
+ * re-acquired — `proper-lockfile`/CC `concurrentSessions` parity.
191
192
  *
192
193
  * This is the family's ONE lock MECHANISM (the per-operation CAS stays in-process: one event loop + a
193
194
  * per-token async mutex, so there is NO per-operation flock). A holder of one of these fences guarantees
@@ -201,7 +202,7 @@ export declare class BootLock {
201
202
  /** The verdict of the ONE release this holding gets, replayed to any later caller (see {@link release}). */
202
203
  private spentVerdict;
203
204
  constructor(lockPath: string);
204
- /** Acquire the lock or throw. Reclaims a lock whose recorded PID is not running (see below). */
205
+ /** Acquire the lock or throw. Reclaims a lock whose recorded OWNER is gone (see below). */
205
206
  acquire(): void;
206
207
  /**
207
208
  * @param depth how many stale reclaim gates deep we already are (see the gate below). Bounded so a
@@ -240,8 +241,8 @@ export declare class BootLock {
240
241
  *
241
242
  * ONE-SHOT, and that is load-bearing. This object stops being a holder the moment it is ASKED to release
242
243
  * — before the filesystem is touched — and a second call REPEATS the first verdict instead of acting on
243
- * it again. A lock file is a bare pid, so two BootLocks over one path in this process are byte-identical
244
- * on disk: after a FAILED release the path can legitimately be re-published (a later acquisition here
244
+ * it again. A lock file names its publishing PROCESS, so two BootLocks over one path in this process are
245
+ * byte-identical on disk: after a FAILED release the path can legitimately be re-published (a later acquisition here
245
246
  * finishing our cleanup), and a stale object that "removed its own lock" a second time would be deleting
246
247
  * the SUCCESSOR's fence while both sides believed they held the directory — the double-writer this whole
247
248
  * mechanism exists to refuse, minted from bookkeeping instead of from a race. So a second release answers
@@ -320,11 +321,86 @@ export declare function acquireStoreDirLock(dir: string, opts: {
320
321
  * stops two processes consolidating the SAME scope at once (the in-process model the file backend assumes —
321
322
  * see {@link BootLock} — does not cover a second OS process with its own timer).
322
323
  *
323
- * `acquire(scope)` writes `lockDir/<sanitizedScope>.consolidate.lock` carrying THIS pid (atomic
324
- * {@link writeThenLink}, so the lock appears with its pid already in it no empty-window prune race). It
325
- * returns a `release` callback on success, or `undefined` when a LIVE owner already holds it (the caller
326
- * treats `undefined` as "busy → skip this pass", a no-op). A STALE lock (recorded pid is dead) is pruned and
327
- * re-acquired once. The implementation lives HERE (the deployment shell's persist/exec axis), NOT core — core
328
- * only DEFINES the injection point (the constitutional split: gate-presence in core, mechanism in the profile).
324
+ * `acquire(scope)` takes a {@link BootLock} over `lockDir/<sanitizedScope>.consolidate.lock`. It returns a
325
+ * `release` callback on success, or `undefined` for EVERY refusal that lock knows how to make a live
326
+ * owner (including this process: a second pass here is still a conflict), a takeover in flight, an
327
+ * unreadable file because the caller's whole vocabulary is "busy → skip this pass", a no-op that costs
328
+ * one deferred consolidation. A stale lock is reclaimed by the same protocol as any other, which is the
329
+ * point of not hand-rolling one: this factory used to prune with a bare `unlink` and re-publish, and two
330
+ * passes that had both read the same dead owner would then both prune and both publish — one deleting the
331
+ * other's fresh lock, leaving two processes consolidating the SAME scope, which is exactly what the lock
332
+ * exists to prevent. The reclaim gate serialises that, the depth cap bounds it, and the identity re-read
333
+ * makes sure a reclaim prunes the owner it came for. One lock MECHANISM in this family, not two.
334
+ *
335
+ * The implementation lives HERE (the deployment shell's persist/exec axis), NOT core — core only DEFINES
336
+ * the injection point (the constitutional split: gate-presence in core, mechanism in the profile).
329
337
  */
330
338
  export declare function createFileConsolidationLock(lockDir: string): (scope: string) => (() => void) | undefined;
339
+ /**
340
+ * A process identity that survives PID REUSE: a pid, WHEN that pid's process started, and the host the
341
+ * observation was made on.
342
+ *
343
+ * A pid alone is not an identity. The OS hands the same number out again once its holder exits, so a lock
344
+ * (or lease, or any other durable "who owns this" record) carrying only a pid says no more than "somebody
345
+ * with that number" — and after a crash, an unrelated process that inherits the number makes the dead
346
+ * owner's record read as a LIVE owner for as long as that process lives. Adding the START TIME makes the
347
+ * record falsifiable: the pid narrows the candidate to one process, the start time decides whether it is
348
+ * still the SAME one.
349
+ *
350
+ * The start time is carried TWICE, against two different clocks, and that is the whole robustness of the
351
+ * thing. Both numbers come from ONE reading of the process's elapsed time, but one is subtracted from the
352
+ * WALL clock and the other from the host's UPTIME — and a platform that leaves one of them unmoved when
353
+ * the wall clock is corrected moves the other. (Where `ps` measures elapsed time against the boot clock, a
354
+ * corrected wall clock shifts the derived wall-clock start and leaves the uptime-relative one alone; where
355
+ * it measures against a stored absolute start, it is the other way round.) A comparison that insists on
356
+ * BOTH before calling a record stale therefore cannot be talked into a reclaim by a clock correction —
357
+ * which matters because that reclaim would be of a LIVE writer's lock. See {@link isProcessLive}.
358
+ *
359
+ * Both are `null` when this host cannot produce a start time (no `ps`, a cut-down `ps` that rejects the
360
+ * query, a probe too slow for its own reading to be worth anything). That is a plain "unknown" — never a
361
+ * zero and never a guess — and every consumer treats an unobtainable fingerprint as UNFALSIFIABLE, keeping
362
+ * the pid-only verdict: the degrade lands on yesterday's behaviour rather than on a wrong reclaim, and
363
+ * says so out loud once.
364
+ */
365
+ export interface ProcessFingerprint {
366
+ /** The process id the rest of the record is about. */
367
+ readonly pid: number;
368
+ /** Start of that pid's process against the WALL clock, to the second; `null` when unobtainable here. */
369
+ readonly startedAtMs: number | null;
370
+ /** The same start against this host's UPTIME (ms after boot), to the second; `null` likewise. */
371
+ readonly startedSinceBootMs: number | null;
372
+ /** The host both were read on — a fingerprint from ANOTHER host is comparable to nothing here. */
373
+ readonly host: string;
374
+ }
375
+ /**
376
+ * The fingerprint of a process: this one when called with no pid, any other pid when named.
377
+ *
378
+ * ONE derivation for both halves of the mechanism — the record a holder WRITES and the probe a later reader
379
+ * verifies it with — on purpose. Deriving our own start time from `process.uptime()` instead would be
380
+ * cheaper, but it measures from Node's own initialisation rather than from the kernel's `exec`, so a busy
381
+ * machine can put the two derivations seconds apart. A reader comparing across them would then read a
382
+ * healthy holder as stale and RECLAIM A LIVE LOCK — a strictly worse failure than the pid reuse this fixes.
383
+ * One derivation makes the tolerance a rounding allowance instead of a guess about bootstrap latency.
384
+ *
385
+ * The self case is memoized: our start time cannot change, the probe is a subprocess, and a lock publish is
386
+ * the only place that needs it — so a process pays for it at most once, whatever it opens.
387
+ */
388
+ export declare function processFingerprint(pid?: number): ProcessFingerprint;
389
+ /**
390
+ * Is the process a record names still the one that wrote it? ONE predicate, two terms: the pid is running,
391
+ * AND the fingerprint has not been falsified.
392
+ *
393
+ * "Not falsified" is the load-bearing wording, and it is what every degrade lands on. A record with no
394
+ * `startedAtMs` (the pre-fingerprint spelling, or a host that cannot produce one), a record written on
395
+ * ANOTHER host (its start time was measured against a different boot — comparing it here would be
396
+ * arithmetic on unrelated numbers), and a probe that cannot answer all reach the same verdict: LIVE. The
397
+ * asymmetry is deliberate and is the whole safety argument — a false "dead" reclaims a directory out from
398
+ * under a working process (two writers, and the ledger loses whichever rows the loser never loaded), while
399
+ * a false "live" costs a refusal an operator can act on.
400
+ */
401
+ export declare function isProcessLive(record: {
402
+ readonly pid: number;
403
+ readonly startedAtMs?: number | null;
404
+ readonly startedSinceBootMs?: number | null;
405
+ readonly host?: string | null;
406
+ }): boolean;