@sema-agent/core 7.5.0 → 7.5.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 (106) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/dist/core/auto-mode.d.ts +9 -0
  3. package/dist/core/auto-mode.js +11 -0
  4. package/dist/core/checkpoint-store.js +5 -1
  5. package/dist/core/protocol-table.d.ts +5 -0
  6. package/dist/core/protocol-table.js +1 -0
  7. package/dist/core/runner/abort-race.d.ts +41 -0
  8. package/dist/core/runner/abort-race.js +38 -0
  9. package/dist/core/runner/checkpoint-scope.d.ts +44 -0
  10. package/dist/core/runner/checkpoint-scope.js +7 -0
  11. package/dist/core/runner/compaction-call-options.d.ts +1 -1
  12. package/dist/core/runner/content-ask-bindings.d.ts +27 -0
  13. package/dist/core/runner/content-ask-bindings.js +1 -0
  14. package/dist/core/runner/contracts.d.ts +1990 -0
  15. package/dist/core/runner/contracts.js +1 -0
  16. package/dist/core/runner/denial-limit-arms.d.ts +24 -1
  17. package/dist/core/runner/denial-limit-arms.js +21 -0
  18. package/dist/core/runner/derived-route-fallback.d.ts +34 -0
  19. package/dist/core/runner/derived-route-fallback.js +16 -0
  20. package/dist/core/runner/inherited-ask-grants.d.ts +46 -0
  21. package/dist/core/runner/inherited-ask-grants.js +29 -0
  22. package/dist/core/runner/park-commit.d.ts +108 -0
  23. package/dist/core/runner/park-commit.js +32 -0
  24. package/dist/core/runner/{prepare-permission-rules.d.ts → permission-rule-lanes.d.ts} +109 -3
  25. package/dist/core/runner/{prepare-permission-rules.js → permission-rule-lanes.js} +47 -1
  26. package/dist/core/runner/prepare-acquire-reconcile.d.ts +1 -1
  27. package/dist/core/runner/prepare-ask-lane.d.ts +110 -0
  28. package/dist/core/runner/prepare-ask-lane.js +133 -0
  29. package/dist/core/runner/prepare-boundary-parks.d.ts +105 -0
  30. package/dist/core/runner/prepare-boundary-parks.js +169 -0
  31. package/dist/core/runner/prepare-caps-and-workflow.d.ts +170 -0
  32. package/dist/core/runner/prepare-caps-and-workflow.js +255 -0
  33. package/dist/core/runner/prepare-config-doors.d.ts +2 -10
  34. package/dist/core/runner/prepare-context-lane.d.ts +119 -0
  35. package/dist/core/runner/prepare-context-lane.js +230 -0
  36. package/dist/core/runner/prepare-defer-classify.d.ts +86 -0
  37. package/dist/core/runner/prepare-defer-classify.js +107 -0
  38. package/dist/core/runner/prepare-delegation-surface.d.ts +104 -0
  39. package/dist/core/runner/prepare-delegation-surface.js +144 -0
  40. package/dist/core/runner/prepare-execution-env.d.ts +54 -0
  41. package/dist/core/runner/prepare-execution-env.js +86 -0
  42. package/dist/core/runner/prepare-file-history.d.ts +95 -0
  43. package/dist/core/runner/prepare-file-history.js +383 -0
  44. package/dist/core/runner/prepare-gate-stations.d.ts +177 -0
  45. package/dist/core/runner/prepare-gate-stations.js +290 -0
  46. package/dist/core/runner/prepare-hands-readface.d.ts +9 -11
  47. package/dist/core/runner/prepare-hands-readface.js +1 -1
  48. package/dist/core/runner/prepare-inherited-gate.d.ts +268 -0
  49. package/dist/core/runner/prepare-inherited-gate.js +266 -0
  50. package/dist/core/runner/prepare-listings.d.ts +77 -0
  51. package/dist/core/runner/prepare-listings.js +76 -0
  52. package/dist/core/runner/prepare-lsp.d.ts +55 -0
  53. package/dist/core/runner/prepare-lsp.js +27 -0
  54. package/dist/core/runner/prepare-memory-engine-session.d.ts +84 -0
  55. package/dist/core/runner/prepare-memory-engine-session.js +233 -0
  56. package/dist/core/runner/prepare-memory.d.ts +1 -1
  57. package/dist/core/runner/prepare-offload-wrappers.d.ts +62 -0
  58. package/dist/core/runner/prepare-offload-wrappers.js +45 -0
  59. package/dist/core/runner/prepare-park-ask.d.ts +164 -0
  60. package/dist/core/runner/prepare-park-ask.js +377 -0
  61. package/dist/core/runner/prepare-policy-chain.d.ts +208 -0
  62. package/dist/core/runner/prepare-policy-chain.js +584 -0
  63. package/dist/core/runner/prepare-project-context.d.ts +119 -0
  64. package/dist/core/runner/prepare-project-context.js +148 -0
  65. package/dist/core/runner/prepare-prompt-assembly.d.ts +95 -0
  66. package/dist/core/runner/prepare-prompt-assembly.js +162 -0
  67. package/dist/core/runner/prepare-prompt-inputs.d.ts +119 -0
  68. package/dist/core/runner/prepare-prompt-inputs.js +141 -0
  69. package/dist/core/runner/prepare-protocol-tools.d.ts +91 -0
  70. package/dist/core/runner/prepare-protocol-tools.js +179 -0
  71. package/dist/core/runner/prepare-question-face.d.ts +101 -0
  72. package/dist/core/runner/prepare-question-face.js +84 -0
  73. package/dist/core/runner/prepare-run-refs.d.ts +89 -0
  74. package/dist/core/runner/prepare-run-refs.js +39 -0
  75. package/dist/core/runner/prepare-safety-scan.d.ts +3 -7
  76. package/dist/core/runner/prepare-safety-scan.js +1 -2
  77. package/dist/core/runner/prepare-suspend-saga.d.ts +170 -0
  78. package/dist/core/runner/prepare-suspend-saga.js +308 -0
  79. package/dist/core/runner/prepare-task.d.ts +14 -1976
  80. package/dist/core/runner/prepare-task.js +182 -5162
  81. package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +111 -0
  82. package/dist/core/runner/prepare-tool-disclosure-mount.js +219 -0
  83. package/dist/core/runner/prepare-turn-wiring.d.ts +154 -0
  84. package/dist/core/runner/prepare-turn-wiring.js +201 -0
  85. package/dist/core/runner/prepare-wiring-manifest.d.ts +192 -0
  86. package/dist/core/runner/prepare-wiring-manifest.js +247 -0
  87. package/dist/core/runner/prepare-workspace-restore.d.ts +3 -56
  88. package/dist/core/runner/prepare-workspace-restore.js +4 -38
  89. package/dist/core/runner/prompt-hash-salt.d.ts +1 -0
  90. package/dist/core/runner/prompt-hash-salt.js +2 -0
  91. package/dist/core/runner/remote-env-retry.d.ts +29 -0
  92. package/dist/core/runner/remote-env-retry.js +16 -0
  93. package/dist/core/runner/rollback-stack.d.ts +32 -0
  94. package/dist/core/runner/rollback-stack.js +30 -0
  95. package/dist/core/runner/runtask.js +1 -1
  96. package/dist/core/runner/workspace-path.d.ts +33 -0
  97. package/dist/core/runner/workspace-path.js +22 -0
  98. package/dist/core/session.d.ts +12 -0
  99. package/dist/core/session.js +3 -0
  100. package/dist/core/tool-policy.d.ts +16 -0
  101. package/dist/core/tool-policy.js +3 -0
  102. package/dist/core/types.d.ts +2 -2
  103. package/dist/core/write-protect.js +3 -2
  104. package/package.json +6 -2
  105. /package/dist/core/runner/{prepare-announce-once.d.ts → announce-once-ledger.d.ts} +0 -0
  106. /package/dist/core/runner/{prepare-announce-once.js → announce-once-ledger.js} +0 -0
@@ -1,60 +1,7 @@
1
- import type { RemoteExecutionEnv, RemoteExecutionError, SnapshotId, VmLifecycleOptions } from "../remote-env.js";
2
1
  import type { ExecutionEnv } from "../../internal/harness.js";
3
- import type { RemoteEnvFailureNote, RunnerDeps, TaskSpec } from "../types.js";
4
- import type { PrepareResume, RunInternals } from "./prepare-task.js";
5
- /**
6
- * rebase one checkpointed absolute path from the OLD workspace root onto the RESTORED
7
- * one (divergent `resumeVM`). POSIX-ONLY by contract: every remote lane's `mountPath` is a container
8
- * path (e2b/k8s/ssh/adb/local-docker are all Linux targets), so a backslash ANYWHERE in the inputs marks
9
- * the value outside this function's domain and it returns `p` UNCHANGED — an un-rebased path is honestly
10
- * observable (the divergence observation already fired) while a WRONGLY-rebased one silently corrupts
11
- * cwd/read-state (r4: drive-root and cross-family recomposition are not implementable without a Windows
12
- * path model no lane needs). Trailing slashes are tolerated (from="/app/" must not weld the suffix);
13
- * bare "/" keeps filesystem-root semantics; a path outside `from` (incl. the prefix-sibling
14
- * "/application" vs "/app") returns unchanged. Exported for direct unit pinning.
15
- */
16
- export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: string): string;
17
- /**
18
- * RB-439-a — project one failed remote lifecycle call onto the caller-facing note. ONE builder for all
19
- * three ops so the `retryable` classification can never drift between the lane that reports a `suspendVM`
20
- * refusal and the lane that reports a failed restore.
21
- */
22
- export declare function remoteEnvFailureNote(op: RemoteEnvFailureNote["op"], error: RemoteExecutionError, attempts: number): RemoteEnvFailureNote;
23
- /**
24
- * RB-439-a — `resumeVM` with a bounded retry on the TRANSIENT codes, plus the attempt count for the
25
- * caller-facing note.
26
- *
27
- * Why this op and no other: `resumeVM` is the one lifecycle call the seam documents as "idempotent +
28
- * re-entrant" (a replica that crashed mid-suspend can be superseded by another resuming the SAME
29
- * snapshot), which is exactly `withRetry`'s red line — retry idempotent establishment, never a
30
- * side-effecting op. `suspendVM` TAKES a snapshot and `postResumeInit` re-injects secrets under an
31
- * ordering red line; neither is retried here, they are disclosed with `retryable` instead so the caller
32
- * decides. A code outside the retryable family (`unsupported`, `auth_failed`) returns on the first
33
- * attempt — `withRetry` will not spend a second call on a permanent refusal.
34
- */
35
- export declare function restoreWorkspaceWithRetry(env: RemoteExecutionEnv, snapshotId: SnapshotId, options: VmLifecycleOptions,
36
- /** design/384 slice 2 (additive): observe each attempt AS IT STARTS. The returned `attempts` is
37
- * only readable after settlement, and a caller whose wait on this promise is BOUNDED (the park
38
- * compensation) must report the live attempt count when its bound fires mid-call — without this
39
- * seat a deaf second attempt was reported as `attempts: 1`. */
40
- onAttempt?: (attempt: number) => void): Promise<{
41
- outcome: Awaited<ReturnType<RemoteExecutionEnv["resumeVM"]>>;
42
- attempts: number;
43
- }>;
44
- /**
45
- * RB-439-c — {@link rebaseWorkspacePath} over a SET of accepted spellings of the old root: the first
46
- * prefix that actually matches wins, and a path under none of them is returned unchanged.
47
- *
48
- * The set exists because "the old root" has no single spelling. `WorkspaceHandle.mountPath` is whatever the
49
- * adapter called the mount, while the persisted paths being migrated were spelled by whoever produced them
50
- * (a `cd` the env canonicalized, a Read key resolved through realpath). On a target where the root has an
51
- * equivalent alias (`/var` ↔ `/private/var`) those disagree while naming the same directory, and a
52
- * single-prefix rebase then matched nothing and silently left the resumed shell in the pre-suspend tree —
53
- * under a disclosure that said the task follows the restored root. Order is caller-chosen (the checkpointed
54
- * spelling first, its canonical form second) and only matters if one prefix is a prefix of another, in
55
- * which case the earlier — more specific — spelling is the intended one. Exported for direct unit pinning.
56
- */
57
- export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
2
+ import type { RunnerDeps, TaskSpec } from "../types.js";
3
+ import type { PrepareResume, RunInternals } from "./contracts.js";
4
+ export { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
58
5
  export interface PrepareWorkspaceRestoreInput {
59
6
  /** borrowed-readonly (the ownership-ref PROTOCOL stays with the driver — D-8 案①): the env this
60
7
  * task owns, when the factory minted one. This phase reads and drives it (`resumeVM`,
@@ -1,41 +1,7 @@
1
- import { isRemoteExecutionEnv, isRetryableRemoteErrorCode, missingRestoreSurface, RETRYABLE_REMOTE_ERROR_CODES } from "../remote-env.js";
2
- import { withRetry } from "../with-retry.js";
3
- export function rebaseWorkspacePath(p, fromRaw, toRaw) {
4
- if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
5
- return p;
6
- const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
7
- const from = stripTrail(fromRaw);
8
- const to = stripTrail(toRaw);
9
- if (p === from || stripTrail(p) === from)
10
- return to;
11
- const fromPrefix = from === "/" ? from : `${from}/`;
12
- if (!p.startsWith(fromPrefix))
13
- return p;
14
- const suffix = p.slice(fromPrefix.length).replace(/^\/+/, "");
15
- return to === "/" ? `${to}${suffix}` : `${to}/${suffix}`;
16
- }
17
- export function remoteEnvFailureNote(op, error, attempts) {
18
- return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
19
- }
20
- const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
21
- const REMOTE_RESTORE_BACKOFF_MS = 200;
22
- export async function restoreWorkspaceWithRetry(env, snapshotId, options, onAttempt) {
23
- let attempts = 0;
24
- const outcome = await withRetry(async (attempt) => {
25
- attempts = attempt;
26
- onAttempt?.(attempt);
27
- return env.resumeVM(snapshotId, options);
28
- }, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
29
- return { outcome, attempts };
30
- }
31
- export function rebaseWorkspacePathAcross(p, froms, to) {
32
- for (const from of froms) {
33
- const out = rebaseWorkspacePath(p, from, to);
34
- if (out !== p)
35
- return out;
36
- }
37
- return p;
38
- }
1
+ import { isRemoteExecutionEnv, missingRestoreSurface } from "../remote-env.js";
2
+ import { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
3
+ import { rebaseWorkspacePathAcross } from "./workspace-path.js";
4
+ export { remoteEnvFailureNote, restoreWorkspaceWithRetry } from "./remote-env-retry.js";
39
5
  export async function prepareWorkspaceRestore(input) {
40
6
  const { ownedEnv, deps, spec, internals, resume, abortController, sessionId } = input;
41
7
  let taskRootPath = input.taskRootInitial;
@@ -0,0 +1 @@
1
+ export declare const PROMPT_HASH_SALT: Buffer<ArrayBufferLike>;
@@ -0,0 +1,2 @@
1
+ import { randomBytes } from "node:crypto";
2
+ export const PROMPT_HASH_SALT = randomBytes(16);
@@ -0,0 +1,29 @@
1
+ import type { RemoteExecutionEnv, RemoteExecutionError, SnapshotId, VmLifecycleOptions } from "../remote-env.js";
2
+ import type { RemoteEnvFailureNote } from "../types.js";
3
+ /**
4
+ * RB-439-a — project one failed remote lifecycle call onto the caller-facing note. ONE builder for all
5
+ * three ops so the `retryable` classification can never drift between the lane that reports a `suspendVM`
6
+ * refusal and the lane that reports a failed restore.
7
+ */
8
+ export declare function remoteEnvFailureNote(op: RemoteEnvFailureNote["op"], error: RemoteExecutionError, attempts: number): RemoteEnvFailureNote;
9
+ /**
10
+ * RB-439-a — `resumeVM` with a bounded retry on the TRANSIENT codes, plus the attempt count for the
11
+ * caller-facing note.
12
+ *
13
+ * Why this op and no other: `resumeVM` is the one lifecycle call the seam documents as "idempotent +
14
+ * re-entrant" (a replica that crashed mid-suspend can be superseded by another resuming the SAME
15
+ * snapshot), which is exactly `withRetry`'s red line — retry idempotent establishment, never a
16
+ * side-effecting op. `suspendVM` TAKES a snapshot and `postResumeInit` re-injects secrets under an
17
+ * ordering red line; neither is retried here, they are disclosed with `retryable` instead so the caller
18
+ * decides. A code outside the retryable family (`unsupported`, `auth_failed`) returns on the first
19
+ * attempt — `withRetry` will not spend a second call on a permanent refusal.
20
+ */
21
+ export declare function restoreWorkspaceWithRetry(env: RemoteExecutionEnv, snapshotId: SnapshotId, options: VmLifecycleOptions,
22
+ /** design/384 slice 2 (additive): observe each attempt AS IT STARTS. The returned `attempts` is
23
+ * only readable after settlement, and a caller whose wait on this promise is BOUNDED (the park
24
+ * compensation) must report the live attempt count when its bound fires mid-call — without this
25
+ * seat a deaf second attempt was reported as `attempts: 1`. */
26
+ onAttempt?: (attempt: number) => void): Promise<{
27
+ outcome: Awaited<ReturnType<RemoteExecutionEnv["resumeVM"]>>;
28
+ attempts: number;
29
+ }>;
@@ -0,0 +1,16 @@
1
+ import { isRetryableRemoteErrorCode, RETRYABLE_REMOTE_ERROR_CODES } from "../remote-env.js";
2
+ import { withRetry } from "../with-retry.js";
3
+ export function remoteEnvFailureNote(op, error, attempts) {
4
+ return { op, code: error.code, retryable: isRetryableRemoteErrorCode(error.code), attempts, message: error.message };
5
+ }
6
+ const REMOTE_RESTORE_MAX_ATTEMPTS = 2;
7
+ const REMOTE_RESTORE_BACKOFF_MS = 200;
8
+ export async function restoreWorkspaceWithRetry(env, snapshotId, options, onAttempt) {
9
+ let attempts = 0;
10
+ const outcome = await withRetry(async (attempt) => {
11
+ attempts = attempt;
12
+ onAttempt?.(attempt);
13
+ return env.resumeVM(snapshotId, options);
14
+ }, { retryableCodes: RETRYABLE_REMOTE_ERROR_CODES, maxAttempts: REMOTE_RESTORE_MAX_ATTEMPTS, backoffMs: () => REMOTE_RESTORE_BACKOFF_MS }, { ...(options.abortSignal !== undefined ? { signal: options.abortSignal } : {}) });
15
+ return { outcome, attempts };
16
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The prepare-FAILURE rollback stack — the implementation of {@link RollbackStack} (declared beside the
3
+ * other prepare contracts in ./contracts.ts, whose JSDoc carries the contract this file keeps).
4
+ *
5
+ * Why a stack and not a `finally`: prepare acquires resources in sequence (an execution env, MCP
6
+ * clients, A2A peers) and every one of them belongs to the RUN once prepare succeeds — the Runner's
7
+ * finish/teardown releases them. Only a prepare that THROWS must release what it already took, in
8
+ * reverse acquisition order. Today the orchestrator sees those handles because they are hoisted
9
+ * `let`s at function scope; a phase module that acquires a handle inside its own body has no such
10
+ * hoist, so it registers the cleanup HERE, immediately after the acquisition, and the driver unwinds
11
+ * the whole stack from its one catch.
12
+ *
13
+ * Posture (identical to the teardown leg the catch already uses — {@link settleTeardownLeg}):
14
+ * · reverse order, each cleanup exactly once — the entry is removed before it runs, so a second
15
+ * `unwindAll` (or a re-entrant one) finds nothing;
16
+ * · no cleanup can stop another — a throw is routed to `report`, a hang is BOUNDED and reported as a
17
+ * `TeardownLegTimeoutError` naming the leg; the unwind proceeds either way;
18
+ * · `unwindAll` NEVER throws — it runs inside the driver's catch, where a throw would replace the
19
+ * prepare error the caller is owed.
20
+ *
21
+ * States: `armed` (accepting pushes) → `committed` (prepare succeeded; the stack is emptied and
22
+ * ownership has passed to `Prepared`) or → `unwinding`/`unwound` (the failure path ran). A `push`
23
+ * outside `armed` is a PROGRAMMING error and is refused loudly: a resource acquired after commit
24
+ * would have no owner on the failure path, and one pushed during an unwind would never run.
25
+ */
26
+ import type { RollbackStack } from "./contracts.js";
27
+ /**
28
+ * Mint one stack per prepare. `report` is the incident channel for a cleanup that throws or times out
29
+ * (wire it to `deps.onError`); with none, failures of the failure path are dropped — the same
30
+ * contract `settleTeardownLeg` states for its own `report`.
31
+ */
32
+ export declare function createRollbackStack(report?: (err: Error) => void): RollbackStack;
@@ -0,0 +1,30 @@
1
+ import { settleTeardownLeg } from "./teardown-bounded.js";
2
+ export function createRollbackStack(report) {
3
+ const cleanups = [];
4
+ let state = "armed";
5
+ return {
6
+ push(cleanup) {
7
+ if (state !== "armed") {
8
+ throw new Error(`RollbackStack.push after ${state}: a cleanup registered once prepare has ${state === "committed" ? "succeeded" : "started unwinding"} would ${state === "committed" ? "never be owned by the failure path" : "never run"} — acquire resources only while the stack is armed.`);
9
+ }
10
+ cleanups.push(cleanup);
11
+ },
12
+ commit() {
13
+ if (state !== "armed")
14
+ throw new Error(`RollbackStack.commit after ${state}: commit is the success path's one disarm and cannot follow an unwind or a second commit.`);
15
+ state = "committed";
16
+ cleanups.length = 0;
17
+ },
18
+ async unwindAll() {
19
+ if (state !== "armed")
20
+ return;
21
+ state = "unwinding";
22
+ const total = cleanups.length;
23
+ let leg = total;
24
+ for (let cleanup = cleanups.pop(); cleanup !== undefined; cleanup = cleanups.pop(), leg--) {
25
+ await settleTeardownLeg(cleanup, `${cleanup.name !== "" ? cleanup.name : "anonymous cleanup"} (rollback leg ${leg} of ${total}, prepare-throw)`, report);
26
+ }
27
+ state = "unwound";
28
+ },
29
+ };
30
+ }
@@ -47,7 +47,7 @@ import { hasBackgroundShell, sweepBackgroundShells } from "../background-shell.j
47
47
  import { cloneObserverInput, formatHookFeedback, hookSeatExpiredError, MAX_HOOK_TIMEOUT_MS, mintHookInvocationIdentity, resolveHookTimeoutMs, runHookSeat } from "../hooks.js";
48
48
  import { buildHumanInputEvent, frameMidTurnUserInput, projectHumanInput } from "../human-input-projection.js";
49
49
  import { delimitUntrusted, inlineUntrusted, REVIEWER_NOTE_MAX_BODY, sanitizeUntrustedText, SHELLED_BODY_ENVELOPE_TAGS } from "../untrusted-text.js";
50
- import { droppedToolDigest, withAnnounceDigests } from "./prepare-announce-once.js";
50
+ import { droppedToolDigest, withAnnounceDigests } from "./announce-once-ledger.js";
51
51
  import { appendInterruptionMarker, reconcileInterruptedSession } from "../session-reconcile.js";
52
52
  import { RunnerSharedToolResultStore } from "../tool-result-store.js";
53
53
  import { formatDiagnosticsBlock } from "../lsp-diagnostics.js";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Pure path arithmetic for a workspace whose ROOT moved: rebase an absolute path from the old root
3
+ * onto the new one. No I/O, no state, no dependency on anything — which is why it lives here rather
4
+ * than inside the restore phase that used to own it. The read-face phase needs the same arithmetic,
5
+ * and a phase reaching sideways into a sibling phase for a helper is the shape the dependency
6
+ * gate exists to stop; the answer is to put the helper where both can reach DOWN to it.
7
+ */
8
+ /**
9
+ * rebase one checkpointed absolute path from the OLD workspace root onto the RESTORED
10
+ * one (divergent `resumeVM`). POSIX-ONLY by contract: every remote lane's `mountPath` is a container
11
+ * path (e2b/k8s/ssh/adb/local-docker are all Linux targets), so a backslash ANYWHERE in the inputs marks
12
+ * the value outside this function's domain and it returns `p` UNCHANGED — an un-rebased path is honestly
13
+ * observable (the divergence observation already fired) while a WRONGLY-rebased one silently corrupts
14
+ * cwd/read-state (r4: drive-root and cross-family recomposition are not implementable without a Windows
15
+ * path model no lane needs). Trailing slashes are tolerated (from="/app/" must not weld the suffix);
16
+ * bare "/" keeps filesystem-root semantics; a path outside `from` (incl. the prefix-sibling
17
+ * "/application" vs "/app") returns unchanged. Exported for direct unit pinning.
18
+ */
19
+ export declare function rebaseWorkspacePath(p: string, fromRaw: string, toRaw: string): string;
20
+ /**
21
+ * RB-439-c — {@link rebaseWorkspacePath} over a SET of accepted spellings of the old root: the first
22
+ * prefix that actually matches wins, and a path under none of them is returned unchanged.
23
+ *
24
+ * The set exists because "the old root" has no single spelling. `WorkspaceHandle.mountPath` is whatever the
25
+ * adapter called the mount, while the persisted paths being migrated were spelled by whoever produced them
26
+ * (a `cd` the env canonicalized, a Read key resolved through realpath). On a target where the root has an
27
+ * equivalent alias (`/var` ↔ `/private/var`) those disagree while naming the same directory, and a
28
+ * single-prefix rebase then matched nothing and silently left the resumed shell in the pre-suspend tree —
29
+ * under a disclosure that said the task follows the restored root. Order is caller-chosen (the checkpointed
30
+ * spelling first, its canonical form second) and only matters if one prefix is a prefix of another, in
31
+ * which case the earlier — more specific — spelling is the intended one. Exported for direct unit pinning.
32
+ */
33
+ export declare function rebaseWorkspacePathAcross(p: string, froms: readonly string[], to: string): string;
@@ -0,0 +1,22 @@
1
+ export function rebaseWorkspacePath(p, fromRaw, toRaw) {
2
+ if (p.includes("\\") || fromRaw.includes("\\") || toRaw.includes("\\"))
3
+ return p;
4
+ const stripTrail = (s) => (s.length > 1 && s.endsWith("/") ? stripTrail(s.slice(0, -1)) : s);
5
+ const from = stripTrail(fromRaw);
6
+ const to = stripTrail(toRaw);
7
+ if (p === from || stripTrail(p) === from)
8
+ return to;
9
+ const fromPrefix = from === "/" ? from : `${from}/`;
10
+ if (!p.startsWith(fromPrefix))
11
+ return p;
12
+ const suffix = p.slice(fromPrefix.length).replace(/^\/+/, "");
13
+ return to === "/" ? `${to}${suffix}` : `${to}/${suffix}`;
14
+ }
15
+ export function rebaseWorkspacePathAcross(p, froms, to) {
16
+ for (const from of froms) {
17
+ const out = rebaseWorkspacePath(p, from, to);
18
+ if (out !== p)
19
+ return out;
20
+ }
21
+ return p;
22
+ }
@@ -312,3 +312,15 @@ export declare function boundedTail(pathEntries: readonly SessionTreeEntry[]): {
312
312
  tail: SessionTreeEntry[];
313
313
  floorEntryId: string | null;
314
314
  };
315
+ /**
316
+ * The ONE "has this session seen conversation?" predicate — the freshness question is asked three
317
+ * times in one prepare (project-memory phase, design/148 center adoption, the epoch-pin
318
+ * discriminator), and an entry-COUNT proxy at ANY of them re-creates the same lie: a host may write
319
+ * session_info / model_change / … rows before the first turn (naming a new chat, recording a model
320
+ * pick), and none of that is evidence of a prior session (5.29 merge-rescan: the count proxy
321
+ * survived at the adoption arm after the pin arm was fixed, silently costing a pre-named session
322
+ * its published center prompt for life). Conversation content — and only that — is the evidence.
323
+ */
324
+ export declare function hasConversationContent(branch: ReadonlyArray<{
325
+ type: string;
326
+ }>): boolean;
@@ -24,3 +24,6 @@ export function boundedTail(pathEntries) {
24
24
  }
25
25
  return { tail: pathEntries.slice(floorIdx), floorEntryId };
26
26
  }
27
+ export function hasConversationContent(branch) {
28
+ return branch.some((e) => e.type === "message" || e.type === "custom_message" || e.type === "compaction");
29
+ }
@@ -1415,6 +1415,22 @@ export declare function withDelegationProvenance(onAsk: OnAsk, delegation: AskDe
1415
1415
  * consultation". A non-wrapper value (including `"allow"`/`"deny"`/undefined) is its own identity.
1416
1416
  */
1417
1417
  export declare function askApproverIdentity(onAsk: unknown): unknown;
1418
+ /**
1419
+ * Is this `onAsk` seat a LIVE APPROVER — a person the run can actually reach?
1420
+ *
1421
+ * Only a function is. A blanket `"allow"`/`"deny"` string is a policy setting (the same reading the
1422
+ * interaction-posture door gives it), and an absent seat is nobody at all; neither can answer an ask, so
1423
+ * neither may outrank a durable park.
1424
+ *
1425
+ * ONE definition on purpose. Two sites must agree on this — the run's own sync-first decline, and the
1426
+ * mandate frozen into the chain its children inherit — and when they were two hand-written booleans they
1427
+ * drifted: the decline narrowed to the function test while the freeze kept testing mere presence, and
1428
+ * each still read correctly on its own, so nothing pointed at the pair. A blanket seat was therefore not
1429
+ * an approver for the parent's asks and an approver for its children's. Declared here, in the vocabulary
1430
+ * beside {@link askApproverIdentity}, because the two sites are now two prepare phases (the inherited-gate
1431
+ * fold and the wiring station) plus the orchestrator, and each reaches DOWN for the one definition.
1432
+ */
1433
+ export declare function isLiveApproverSeat(onAsk: OnAsk | undefined): boolean;
1418
1434
  /** EXACT structural clone for the approval boundary (alias isolation) — fail-closed, never lossy,
1419
1435
  * never identity: a JSON round-trip would silently DROP unclonable members (presented ≠ executing =
1420
1436
  * consent misrepresentation) and an identity fallback would hand the approver the live executing
@@ -847,6 +847,9 @@ export function withDelegationProvenance(onAsk, delegation) {
847
847
  export function askApproverIdentity(onAsk) {
848
848
  return typeof onAsk === "function" ? (delegatedApproverRoot.get(onAsk) ?? onAsk) : onAsk;
849
849
  }
850
+ export function isLiveApproverSeat(onAsk) {
851
+ return typeof onAsk === "function";
852
+ }
850
853
  export function tryCloneArgs(v) {
851
854
  try {
852
855
  const value = structuredClone(v);
@@ -662,7 +662,7 @@ export interface ToolExecuteContext {
662
662
  * at all; a park under a screening entry announces the requirement on `onError`
663
663
  * (classification `screening-constraint-in-durable-chain`) at park time.
664
664
  */
665
- inheritedGate?: import("./runner/prepare-task.js").InheritedGate;
665
+ inheritedGate?: import("./runner/contracts.js").InheritedGate;
666
666
  };
667
667
  /**
668
668
  * design/176 — the peer-chain SEED for a message-driven (tier-3) revival: the hop chain of the
@@ -1046,7 +1046,7 @@ export interface ToolExecuteContext {
1046
1046
  * read-only, NEVER a model/tool argument (same posture as {@link activeSkillScope}). Undefined when the
1047
1047
  * tool runs outside a Runner task.
1048
1048
  */
1049
- inheritedGateForChildren?: () => import("./runner/prepare-task.js").InheritedGate;
1049
+ inheritedGateForChildren?: () => import("./runner/contracts.js").InheritedGate;
1050
1050
  /**
1051
1051
  * design/180 half A — the delegation runtime-provenance ARMING face. Runner-filled; a delegation
1052
1052
  * tool calls it at spawn time: a non-undefined return means this (parent) run is armed (it mounts
@@ -1,4 +1,4 @@
1
- import { isWinFormPath, writeTargetPath } from "../tools/fs/safety.js";
1
+ import { isWinFormPath, writeTargetPath, isUncPath } from "../tools/fs/safety.js";
2
2
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./runner/session-rule-policy.js";
3
3
  import { dataRootSpellings } from "./sensitive-path-policy.js";
4
4
  const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
@@ -137,7 +137,8 @@ function pathViewSegments(path, win32, seps = BOTH_SEPARATOR_FAMILIES, anchorFlo
137
137
  const BOTH_SEPARATOR_FAMILIES = /[\\/]/;
138
138
  const POSIX_SEPARATOR_FAMILY = /\//;
139
139
  function uncAnchorSegments(path) {
140
- return /^[\\/]{2}[^\\/]/.test(path) ? 2 : 0;
140
+ const third = path[2];
141
+ return isUncPath(path) && third !== undefined && third !== "\\" && third !== "/" ? 2 : 0;
141
142
  }
142
143
  function relativeUnderDataRoot(path, roots) {
143
144
  for (const root of roots) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.5.0",
3
+ "version": "7.5.2",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -73,7 +73,11 @@
73
73
  "gate:error-surface": "node scripts/verify-error-surface.mjs",
74
74
  "gate:criteria": "node scripts/criteria-lint.mjs",
75
75
  "gate:reminder-literal": "node scripts/verify-reminder-literal.mjs",
76
- "gate:wire": "node scripts/wire-changes.mjs"
76
+ "gate:wire": "node scripts/wire-changes.mjs",
77
+ "gate:layering": "node scripts/verify-layering.mjs",
78
+ "gate:phase-api": "node scripts/verify-phase-api.mjs",
79
+ "gate:agent-returns": "node scripts/verify-agent-returns.mjs",
80
+ "gate:all": "node scripts/gate-all.mjs"
77
81
  },
78
82
  "dependencies": {
79
83
  "@modelcontextprotocol/sdk": "1.30.0",