@sema-agent/core 5.46.0 → 5.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +93 -0
- package/dist/agents/agent-transcript-tool.d.ts +4 -0
- package/dist/agents/agent-transcript-tool.js +10 -3
- package/dist/agents/send-message-tool.d.ts +43 -1
- package/dist/agents/send-message-tool.js +50 -11
- package/dist/agents/subagent.d.ts +18 -0
- package/dist/agents/subagent.js +231 -4
- package/dist/config/defaults.d.ts +20 -0
- package/dist/config/defaults.js +5 -0
- package/dist/core/background-agent-store.d.ts +1 -0
- package/dist/core/background-agent-store.js +13 -0
- package/dist/core/governance-codes.d.ts +13 -0
- package/dist/core/governance-codes.js +33 -0
- package/dist/core/mcp.d.ts +6 -1
- package/dist/core/mcp.js +34 -7
- package/dist/core/memory-engine/delegation-settlement.d.ts +318 -0
- package/dist/core/memory-engine/delegation-settlement.js +661 -0
- package/dist/core/memory-engine/engine.d.ts +159 -1
- package/dist/core/memory-engine/engine.js +699 -15
- package/dist/core/memory-engine/file-backend.d.ts +1 -0
- package/dist/core/memory-engine/file-backend.js +3 -1
- package/dist/core/memory-engine/frontmatter.d.ts +46 -19
- package/dist/core/memory-engine/frontmatter.js +91 -77
- package/dist/core/memory-engine/index.d.ts +4 -3
- package/dist/core/memory-engine/index.js +3 -2
- package/dist/core/memory-engine/layout.d.ts +14 -0
- package/dist/core/memory-engine/layout.js +2 -2
- package/dist/core/memory-engine/memory-backend-contract.js +43 -0
- package/dist/core/memory-engine/origin-clearance.d.ts +66 -0
- package/dist/core/memory-engine/origin-clearance.js +84 -0
- package/dist/core/memory-engine/provenance-wording.d.ts +50 -0
- package/dist/core/memory-engine/provenance-wording.js +15 -0
- package/dist/core/memory-engine/tools.d.ts +61 -7
- package/dist/core/memory-engine/tools.js +34 -9
- package/dist/core/memory-engine/types.d.ts +70 -2
- package/dist/core/reminder-disclosure.d.ts +90 -0
- package/dist/core/reminder-disclosure.js +64 -0
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +6 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +1 -1
- package/dist/core/runner/prepare-hands-readface.d.ts +4 -0
- package/dist/core/runner/prepare-hands-readface.js +1 -0
- package/dist/core/runner/prepare-memory.js +50 -15
- package/dist/core/runner/prepare-task.d.ts +39 -0
- package/dist/core/runner/prepare-task.js +128 -40
- package/dist/core/runner/runtask.js +3 -1
- package/dist/core/session-reconcile.js +3 -2
- package/dist/core/session-store.d.ts +59 -1
- package/dist/core/session-store.js +82 -14
- package/dist/core/session.d.ts +83 -1
- package/dist/core/task-registry-agent.d.ts +28 -0
- package/dist/core/task-registry-agent.js +63 -2
- package/dist/core/task-registry.d.ts +21 -0
- package/dist/core/task-registry.js +4 -1
- package/dist/core/types.d.ts +98 -3
- package/dist/core/types.js +3 -0
- package/dist/core/untrusted-text.d.ts +63 -0
- package/dist/core/untrusted-text.js +48 -0
- package/dist/core/wiring-manifest.d.ts +35 -0
- package/dist/core/wiring-manifest.js +21 -1
- package/dist/engine/harness/types.d.ts +36 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +6 -4
- package/dist/internal/harness-types.d.ts +1 -0
- package/dist/stores/file/index.d.ts +19 -3
- package/dist/stores/file/index.js +24 -1
- package/dist/stores/file/session-store.d.ts +18 -4
- package/dist/stores/file/session-store.js +73 -12
- package/dist/tools/fs/fs-pdf.d.ts +12 -1
- package/dist/tools/fs/fs-pdf.js +17 -3
- package/dist/tools/fs/fs-read.d.ts +2 -1
- package/dist/tools/fs/fs-read.js +33 -5
- package/dist/tools/fs/fs-shared.d.ts +6 -2
- package/dist/tools/fs/index.d.ts +7 -0
- package/dist/tools/fs/index.js +1 -1
- package/dist/tools/task-list.d.ts +5 -1
- package/dist/tools/web.js +21 -2
- package/package.json +3 -2
- package/test/export-surface.snapshot.json +24 -2
|
@@ -3446,7 +3446,8 @@ export class Runner {
|
|
|
3446
3446
|
gates: [...prepared.humanReviewRef.gates],
|
|
3447
3447
|
};
|
|
3448
3448
|
}
|
|
3449
|
-
|
|
3449
|
+
const reminderDisclosuresActive = Object.keys(prepared.reminderDisclosureCounts).length > 0;
|
|
3450
|
+
if (rs.counters.finalVerifyInjections > 0 || rs.attach.attachmentsInjected > 0 || rs.counters.repetitionCuts > 0 || rs.counters.repetitionSpared > 0 || rs.counters.approachNoticesSent > 0 || reminderDisclosuresActive) {
|
|
3450
3451
|
stats.mechanisms = {
|
|
3451
3452
|
...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjected: true } : {}),
|
|
3452
3453
|
...(rs.counters.finalVerifyInjections > 0 ? { finalVerifyInjections: rs.counters.finalVerifyInjections } : {}),
|
|
@@ -3455,6 +3456,7 @@ export class Runner {
|
|
|
3455
3456
|
...(rs.counters.repetitionCuts > 0 ? { repetitionCuts: rs.counters.repetitionCuts } : {}),
|
|
3456
3457
|
...(rs.counters.repetitionSpared > 0 ? { repetitionSpared: rs.counters.repetitionSpared } : {}),
|
|
3457
3458
|
...(rs.counters.repetitionEvents.length > 0 ? { repetitionEvents: rs.counters.repetitionEvents } : {}),
|
|
3459
|
+
...(reminderDisclosuresActive ? { reminderDisclosures: { ...prepared.reminderDisclosureCounts } } : {}),
|
|
3458
3460
|
};
|
|
3459
3461
|
}
|
|
3460
3462
|
const result = assembleResult(spec, prepared.sessionId, final, stats, {
|
|
@@ -9,7 +9,8 @@ const INTERRUPTED_IDEMPOTENT = "[INTERRUPTED] The previous run ended before this
|
|
|
9
9
|
"may not have taken effect. It is safe to REPLAY: re-issuing the same call converges the state to " +
|
|
10
10
|
"what you intended. If you decide not to re-issue it, verify the current state first.";
|
|
11
11
|
const INTERRUPTED_NEVER_STARTED = "[INTERRUPTED] The run was aborted before this tool call started. It was never executed and had " +
|
|
12
|
-
"no side effects
|
|
12
|
+
"no side effects. If the run was stopped by a pending approval or an unresolved gate, wait for it " +
|
|
13
|
+
"to be resolved before re-issuing; re-issuing is otherwise safe if you still need this call.";
|
|
13
14
|
const INTERRUPTED_REPEAT_UNKNOWN = "[INTERRUPTED] Same as an earlier interrupted call in this batch: no result was recorded and the outcome is " +
|
|
14
15
|
"UNKNOWN — verify before relying on it.";
|
|
15
16
|
const INTERRUPTED_REPEAT_SAFE = "[INTERRUPTED] Same as an earlier interrupted call in this batch: this tool is read-only/idempotent, so just " +
|
|
@@ -17,7 +18,7 @@ const INTERRUPTED_REPEAT_SAFE = "[INTERRUPTED] Same as an earlier interrupted ca
|
|
|
17
18
|
const INTERRUPTED_REPEAT_IDEMPOTENT = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it is safe to REPLAY — re-issue it, or verify " +
|
|
18
19
|
"the current state if you decide not to.";
|
|
19
20
|
const INTERRUPTED_REPEAT_NEVER_STARTED = "[INTERRUPTED] Same as an earlier interrupted call in this batch: it never started and had no side effects — " +
|
|
20
|
-
"safe
|
|
21
|
+
"re-issuing is safe once whatever stopped the run is resolved.";
|
|
21
22
|
const REPEAT_TEXT = {
|
|
22
23
|
never_started: INTERRUPTED_REPEAT_NEVER_STARTED,
|
|
23
24
|
read: INTERRUPTED_REPEAT_SAFE,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type SessionRepo } from "../internal/harness.js";
|
|
2
|
-
import { type AcquiredSession, type SessionStore, type SessionStoreSummary } from "./session.js";
|
|
2
|
+
import { type AcquiredSession, type PlacedSessionRow, type SessionPlacement, type SessionStore, type SessionStoreSummary } from "./session.js";
|
|
3
3
|
import type { StoreDurability } from "./checkpoint-store.js";
|
|
4
4
|
import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
|
|
5
5
|
export { SESSION_DEFAULT_TTL_DAYS };
|
|
@@ -36,6 +36,21 @@ export interface TtlSessionStoreOptions {
|
|
|
36
36
|
* fail-closed as process-local (under-promise, never over-promise).
|
|
37
37
|
*/
|
|
38
38
|
durability?: StoreDurability;
|
|
39
|
+
/**
|
|
40
|
+
* Subagent transcript persistence — the store's per-placement durability declaration (see
|
|
41
|
+
* {@link SessionStore.placements}). Same declaration rule as {@link durability}: this cache layer
|
|
42
|
+
* cannot inspect whether the injected repo persists placed sessions, so the DEPLOYMENT that wired
|
|
43
|
+
* the repo states the fact (`FileStorageBackend` passes `{subagent:{durability:"durable"}}`).
|
|
44
|
+
* Declaring it also arms the cache-layer placement obligations (list exclusion, claim refusal,
|
|
45
|
+
* release-is-deletion, sweep-never-deletes) — they key on the repo-persisted tuple, which a repo
|
|
46
|
+
* that ignores `placement` never mints, so declaring over such a repo is the deployment lying to
|
|
47
|
+
* itself (declaration-制, wiring-manifest posture).
|
|
48
|
+
*/
|
|
49
|
+
placements?: {
|
|
50
|
+
subagent?: {
|
|
51
|
+
durability: StoreDurability;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
39
54
|
}
|
|
40
55
|
/**
|
|
41
56
|
* TTL-cached {@link SessionStore} over a pluggable {@link SessionRepo}.
|
|
@@ -59,6 +74,18 @@ export declare class TtlSessionStore implements SessionStore {
|
|
|
59
74
|
readonly retention: "none";
|
|
60
75
|
/** design/173 §2.3 — see {@link TtlSessionStoreOptions.durability} for the declaration rules. */
|
|
61
76
|
readonly durability?: StoreDurability;
|
|
77
|
+
/** Subagent transcript persistence — see {@link TtlSessionStoreOptions.placements}. */
|
|
78
|
+
readonly placements?: {
|
|
79
|
+
subagent?: {
|
|
80
|
+
durability: StoreDurability;
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
/** Placed-partition enumeration, present only when the backing repo can enumerate placements
|
|
84
|
+
* (see {@link SessionStore.listPlaced} — absence means the partition-GC leg is unavailable). */
|
|
85
|
+
readonly listPlaced?: (kind: "subagent", opts?: {
|
|
86
|
+
olderThanMs?: number;
|
|
87
|
+
scope?: string;
|
|
88
|
+
}) => Promise<PlacedSessionRow[]>;
|
|
62
89
|
private repo;
|
|
63
90
|
private entries;
|
|
64
91
|
/** Session-ownership rows (the multi-tenant admission capability face — see SessionStore.ownerOf).
|
|
@@ -68,6 +95,20 @@ export declare class TtlSessionStore implements SessionStore {
|
|
|
68
95
|
private owners;
|
|
69
96
|
/** In-flight acquisitions keyed by id, so concurrent acquire(sameId) share one session. */
|
|
70
97
|
private pending;
|
|
98
|
+
/** Subagent transcript persistence (codex 323-r2) — placed ids whose durable deletion FAILED: the
|
|
99
|
+
* placed fact for the retry lives HERE, not on a retained cache entry, because the repo may have
|
|
100
|
+
* torn down the shared session authority before its removal failed (the File repo closes the
|
|
101
|
+
* append fd pre-rmSync) — a retained entry would serve a session whose log is closed while the
|
|
102
|
+
* file still exists (append → log_closed, transcript revival broken until restart). The entry is
|
|
103
|
+
* dropped instead, so a re-acquire replays a FRESH authority from disk; success clears the row. */
|
|
104
|
+
private pendingPlacedDeletes;
|
|
105
|
+
/** Subagent transcript persistence (codex 323-r3) — placed deletions IN FLIGHT: acquire awaits the
|
|
106
|
+
* settlement before resolving the id, because the repo's delete tears down the shared session
|
|
107
|
+
* authority mid-flight — an acquire racing the await window could capture (or rebuild and then
|
|
108
|
+
* lose) an authority the delete is about to close, and the caller would hold a session whose
|
|
109
|
+
* first append answers log_closed while the file still exists. The gate is per-id and bounded by
|
|
110
|
+
* the delete's own settlement (both outcomes release it). */
|
|
111
|
+
private deletingPlaced;
|
|
71
112
|
/** Sessions pinned by a design/45 checkpoint — skipped by idle sweep until unpinned (B6/§5). */
|
|
72
113
|
private pinned;
|
|
73
114
|
private defaultTtlMs;
|
|
@@ -77,7 +118,24 @@ export declare class TtlSessionStore implements SessionStore {
|
|
|
77
118
|
/** Get an existing session by id (resuming from the repo if needed), or create one. */
|
|
78
119
|
acquire(sessionId?: string, opts?: {
|
|
79
120
|
requireExisting?: boolean;
|
|
121
|
+
placement?: SessionPlacement;
|
|
80
122
|
}): Promise<AcquiredSession>;
|
|
123
|
+
/**
|
|
124
|
+
* Placement obligation 6 (claim closure): an EXISTING placed session refuses the claim-form
|
|
125
|
+
* acquire — no `requireExisting` (the read/revive form, obligation 5) and no creation placement
|
|
126
|
+
* (the trusted-internals form; the creating call and its dedup/retry re-acquires stay legal, and
|
|
127
|
+
* placement immutability means the argument can never REWRITE anything — the persisted tuple wins).
|
|
128
|
+
*
|
|
129
|
+
* …and the MIRROR direction (codex 323-r1 F1 — the pre-claim SQUAT race): the child session id is
|
|
130
|
+
* published on the spawn frame BEFORE the child's prepare creates the session, so a concurrent
|
|
131
|
+
* claim-form acquire could win first creation and hand the trusted placement caller an ORDINARY
|
|
132
|
+
* session — host-listable, never really-deleted, the declared full tier falsified in silence. On
|
|
133
|
+
* a store that DECLARES the partition (an ignoring store legally drops placements), an acquire
|
|
134
|
+
* carrying a placement must therefore come back with a PERSISTED tuple whose join identity
|
|
135
|
+
* matches the declaration — a missing or foreign tuple refuses loudly, on every resolution path
|
|
136
|
+
* (cache hit, pending-promise inherit, open, idempotent re-create).
|
|
137
|
+
*/
|
|
138
|
+
private assertPlacementAdmission;
|
|
81
139
|
/** Resume an existing session from the repo, or create it if the backend has no such id (unless
|
|
82
140
|
* `requireExisting`, in which case a genuinely-missing id fails loud instead — design/114 Phase3). */
|
|
83
141
|
private openOrCreate;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InMemorySessionRepo, uuidv7, } from "../internal/harness.js";
|
|
1
|
+
import { InMemorySessionRepo, SessionError, uuidv7, } from "../internal/harness.js";
|
|
2
2
|
import { isSessionConflict } from "./session.js";
|
|
3
3
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
4
4
|
import { SESSION_DEFAULT_TTL_DAYS } from "../config/defaults.js";
|
|
@@ -6,10 +6,14 @@ export { SESSION_DEFAULT_TTL_DAYS };
|
|
|
6
6
|
export class TtlSessionStore {
|
|
7
7
|
retention = "none";
|
|
8
8
|
durability;
|
|
9
|
+
placements;
|
|
10
|
+
listPlaced;
|
|
9
11
|
repo;
|
|
10
12
|
entries = new Map();
|
|
11
13
|
owners = new Map();
|
|
12
14
|
pending = new Map();
|
|
15
|
+
pendingPlacedDeletes = new Set();
|
|
16
|
+
deletingPlaced = new Map();
|
|
13
17
|
pinned = new Set();
|
|
14
18
|
defaultTtlMs;
|
|
15
19
|
evictPolicy;
|
|
@@ -21,6 +25,12 @@ export class TtlSessionStore {
|
|
|
21
25
|
this.durability = durability;
|
|
22
26
|
this.defaultTtlMs = (opts.defaultTtlDays ?? SESSION_DEFAULT_TTL_DAYS) * DAY_MS;
|
|
23
27
|
this.evictPolicy = opts.evict ?? (opts.repo ? "forget" : "delete");
|
|
28
|
+
if (opts.placements !== undefined)
|
|
29
|
+
this.placements = opts.placements;
|
|
30
|
+
const repoListPlaced = this.repo.listPlaced;
|
|
31
|
+
if (typeof repoListPlaced === "function") {
|
|
32
|
+
this.listPlaced = (kind, o) => repoListPlaced.call(this.repo, kind, o);
|
|
33
|
+
}
|
|
24
34
|
if (opts.sweepIntervalMs !== undefined && opts.sweepIntervalMs > 0) {
|
|
25
35
|
this.sweepTimer = setInterval(() => this.sweep(), opts.sweepIntervalMs);
|
|
26
36
|
this.sweepTimer.unref?.();
|
|
@@ -29,8 +39,12 @@ export class TtlSessionStore {
|
|
|
29
39
|
async acquire(sessionId, opts) {
|
|
30
40
|
this.sweep();
|
|
31
41
|
if (sessionId) {
|
|
42
|
+
const inflightPlacedDelete = this.deletingPlaced.get(sessionId);
|
|
43
|
+
if (inflightPlacedDelete !== undefined)
|
|
44
|
+
await inflightPlacedDelete;
|
|
32
45
|
const existing = this.entries.get(sessionId);
|
|
33
46
|
if (existing) {
|
|
47
|
+
this.assertPlacementAdmission(sessionId, existing.placement, opts);
|
|
34
48
|
existing.lastActiveAt = Date.now();
|
|
35
49
|
return { session: existing.session, sessionId };
|
|
36
50
|
}
|
|
@@ -39,26 +53,46 @@ export class TtlSessionStore {
|
|
|
39
53
|
}
|
|
40
54
|
const inflight = this.pending.get(sessionId);
|
|
41
55
|
if (inflight) {
|
|
42
|
-
|
|
56
|
+
const session = await inflight;
|
|
57
|
+
this.assertPlacementAdmission(sessionId, this.entries.get(sessionId)?.placement, opts);
|
|
58
|
+
return { session, sessionId };
|
|
43
59
|
}
|
|
44
|
-
const p = this.openOrCreate(sessionId, false);
|
|
60
|
+
const p = this.openOrCreate(sessionId, false, opts?.placement);
|
|
45
61
|
this.pending.set(sessionId, p);
|
|
46
62
|
try {
|
|
47
|
-
|
|
63
|
+
const session = await p;
|
|
64
|
+
this.assertPlacementAdmission(sessionId, this.entries.get(sessionId)?.placement, opts);
|
|
65
|
+
return { session, sessionId };
|
|
48
66
|
}
|
|
49
67
|
finally {
|
|
50
68
|
this.pending.delete(sessionId);
|
|
51
69
|
}
|
|
52
70
|
}
|
|
53
|
-
const session = await this.createAndStore(undefined);
|
|
71
|
+
const session = await this.createAndStore(undefined, opts?.placement);
|
|
54
72
|
const meta = await session.getMetadata();
|
|
55
73
|
return { session, sessionId: meta.id };
|
|
56
74
|
}
|
|
57
|
-
|
|
75
|
+
assertPlacementAdmission(sessionId, placement, opts) {
|
|
76
|
+
if (placement !== undefined && opts?.requireExisting !== true && opts?.placement === undefined) {
|
|
77
|
+
throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${placement.kind} placement partition — a claim-form acquire cannot attach to it (read it with requireExisting, or address the agent through its a* handle)`);
|
|
78
|
+
}
|
|
79
|
+
if (opts?.placement !== undefined && opts.requireExisting !== true && this.placements?.subagent !== undefined) {
|
|
80
|
+
const want = opts.placement;
|
|
81
|
+
if (placement === undefined) {
|
|
82
|
+
throw new SessionError("placement_refused", `session ${sessionId} exists OUTSIDE the ${want.kind} placement partition it was declared into — refusing to run a subagent transcript on it (a concurrent claim-form create won this id, or the repo dropped the tuple)`);
|
|
83
|
+
}
|
|
84
|
+
if (placement.kind !== want.kind || placement.scope !== want.scope || placement.handle !== want.handle) {
|
|
85
|
+
throw new SessionError("placement_refused", `session ${sessionId}'s persisted placement does not match the declared one (persisted ${placement.kind}/${placement.scope ?? "-"}/${placement.handle ?? "-"} vs declared ${want.kind}/${want.scope ?? "-"}/${want.handle ?? "-"}) — refusing a cross-tuple attach`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async openOrCreate(id, requireExisting = false, placement) {
|
|
58
90
|
try {
|
|
59
91
|
const session = await this.repo.open({ id, createdAt: "" });
|
|
60
92
|
const meta = await session.getMetadata();
|
|
61
|
-
|
|
93
|
+
if (meta.placement === undefined)
|
|
94
|
+
this.pendingPlacedDeletes.delete(id);
|
|
95
|
+
this.entries.set(id, { session, lastActiveAt: Date.now(), createdAt: meta.createdAt, ...(meta.forkedFrom !== undefined ? { forkedFrom: meta.forkedFrom } : {}), ...(meta.placement !== undefined ? { placement: meta.placement } : {}) });
|
|
62
96
|
return session;
|
|
63
97
|
}
|
|
64
98
|
catch (err) {
|
|
@@ -68,13 +102,15 @@ export class TtlSessionStore {
|
|
|
68
102
|
if (requireExisting && isNotFound(err)) {
|
|
69
103
|
throw err;
|
|
70
104
|
}
|
|
71
|
-
return this.createAndStore(id);
|
|
105
|
+
return this.createAndStore(id, placement);
|
|
72
106
|
}
|
|
73
107
|
}
|
|
74
|
-
async createAndStore(id) {
|
|
75
|
-
const session = await this.repo.create(id ? { id } : {});
|
|
108
|
+
async createAndStore(id, placement) {
|
|
109
|
+
const session = await this.repo.create({ ...(id !== undefined ? { id } : {}), ...(placement !== undefined ? { placement } : {}) });
|
|
76
110
|
const meta = await session.getMetadata();
|
|
77
|
-
|
|
111
|
+
if (meta.placement === undefined)
|
|
112
|
+
this.pendingPlacedDeletes.delete(meta.id);
|
|
113
|
+
this.entries.set(meta.id, { session, lastActiveAt: Date.now(), createdAt: meta.createdAt, ...(meta.forkedFrom !== undefined ? { forkedFrom: meta.forkedFrom } : {}), ...(meta.placement !== undefined ? { placement: meta.placement } : {}) });
|
|
78
114
|
return session;
|
|
79
115
|
}
|
|
80
116
|
touch(sessionId) {
|
|
@@ -92,6 +128,7 @@ export class TtlSessionStore {
|
|
|
92
128
|
}
|
|
93
129
|
async list() {
|
|
94
130
|
return [...this.entries.entries()]
|
|
131
|
+
.filter(([, e]) => e.placement === undefined)
|
|
95
132
|
.map(([sessionId, e]) => ({
|
|
96
133
|
sessionId,
|
|
97
134
|
lastActiveAt: e.lastActiveAt,
|
|
@@ -137,12 +174,43 @@ export class TtlSessionStore {
|
|
|
137
174
|
return;
|
|
138
175
|
}
|
|
139
176
|
const e = this.entries.get(sessionId);
|
|
140
|
-
if (
|
|
177
|
+
if (e !== undefined && e.placement === undefined)
|
|
178
|
+
this.pendingPlacedDeletes.delete(sessionId);
|
|
179
|
+
let placed = e !== undefined ? e.placement !== undefined : this.pendingPlacedDeletes.has(sessionId);
|
|
180
|
+
if (!placed && e === undefined && this.placements !== undefined) {
|
|
181
|
+
const probe = this.repo.placementOf;
|
|
182
|
+
if (typeof probe === "function") {
|
|
183
|
+
try {
|
|
184
|
+
placed = (await probe.call(this.repo, sessionId)) !== undefined;
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
placed = false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (!e && !placed) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (placed) {
|
|
195
|
+
this.pendingPlacedDeletes.add(sessionId);
|
|
196
|
+
this.entries.delete(sessionId);
|
|
197
|
+
let settleGate;
|
|
198
|
+
this.deletingPlaced.set(sessionId, new Promise((r) => (settleGate = r)));
|
|
199
|
+
try {
|
|
200
|
+
const meta = e !== undefined ? await e.session.getMetadata() : { id: sessionId, createdAt: "" };
|
|
201
|
+
await this.repo.delete(meta);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
this.deletingPlaced.delete(sessionId);
|
|
205
|
+
settleGate();
|
|
206
|
+
}
|
|
207
|
+
this.pendingPlacedDeletes.delete(sessionId);
|
|
208
|
+
this.owners.delete(sessionId);
|
|
141
209
|
return;
|
|
142
210
|
}
|
|
143
211
|
this.entries.delete(sessionId);
|
|
144
212
|
if (this.evictPolicy === "delete") {
|
|
145
|
-
await this.repo.delete(await e.session.getMetadata());
|
|
213
|
+
await this.repo.delete(e !== undefined ? await e.session.getMetadata() : { id: sessionId, createdAt: "" });
|
|
146
214
|
this.owners.delete(sessionId);
|
|
147
215
|
}
|
|
148
216
|
}
|
|
@@ -174,7 +242,7 @@ export class TtlSessionStore {
|
|
|
174
242
|
}
|
|
175
243
|
if (now - e.lastActiveAt > this.defaultTtlMs) {
|
|
176
244
|
this.entries.delete(id);
|
|
177
|
-
if (this.evictPolicy === "delete") {
|
|
245
|
+
if (this.evictPolicy === "delete" && e.placement === undefined) {
|
|
178
246
|
void this.repo.delete({ id, createdAt: "" }).then(() => {
|
|
179
247
|
this.owners.delete(id);
|
|
180
248
|
}, () => { });
|
package/dist/core/session.d.ts
CHANGED
|
@@ -7,8 +7,43 @@ export { SessionError } from "../internal/harness.js";
|
|
|
7
7
|
export type { SessionWriteOptions } from "../internal/harness.js";
|
|
8
8
|
import type { Session } from "../internal/harness.js";
|
|
9
9
|
import type { SessionTreeEntry } from "../internal/harness.js";
|
|
10
|
+
export type { SessionPlacementRecord } from "../internal/harness.js";
|
|
10
11
|
/** True when `err` is a session optimistic-lock conflict (a concurrent writer won the branch leaf). */
|
|
11
12
|
export declare function isSessionConflict(err: unknown): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Subagent transcript persistence — the ACQUIRE-form placement declaration a trusted spawner passes
|
|
15
|
+
* when it creates a subagent's transcript session: the session is created INTO the store's subagent
|
|
16
|
+
* partition instead of the host lane. The minter reports the fact; the store never guesses residency
|
|
17
|
+
* from call shape. `placedAt` is stamped by the store at creation (see
|
|
18
|
+
* {@link SessionPlacementRecord}). Reaches stores ONLY through the engine's trusted internals chain
|
|
19
|
+
* (never a public TaskSpec key — a public key would let any caller push arbitrary sessions into the
|
|
20
|
+
* partition and poison store-side retention/enumeration).
|
|
21
|
+
*/
|
|
22
|
+
export interface SessionPlacement {
|
|
23
|
+
kind: "subagent";
|
|
24
|
+
/** Tenant key, same source as the durable agent row's `scope`. */
|
|
25
|
+
scope?: string;
|
|
26
|
+
/** The spawning host session (retrieval/partition metadata; authorization stays on the row predicate). */
|
|
27
|
+
parentSessionId?: string;
|
|
28
|
+
/** The delegation tree's root host session (fixed point). */
|
|
29
|
+
rootSessionId?: string;
|
|
30
|
+
/** The durable agent row key (`a…`) — the store-side join key for orphan GC. */
|
|
31
|
+
handle?: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One row of {@link SessionStore.listPlaced}. The row-store join key is the FULL `(scope, handle)`
|
|
35
|
+
* tuple — a listing that cannot supply both returns the discriminated `tupleIncomplete` variant
|
|
36
|
+
* instead (consumers fail closed on it: never age-reaped, reported honestly).
|
|
37
|
+
*/
|
|
38
|
+
export type PlacedSessionRow = {
|
|
39
|
+
sessionId: string;
|
|
40
|
+
placedAt: number;
|
|
41
|
+
} & ({
|
|
42
|
+
scope: string;
|
|
43
|
+
handle: string;
|
|
44
|
+
} | {
|
|
45
|
+
tupleIncomplete: true;
|
|
46
|
+
});
|
|
12
47
|
/** A session handed back by a {@link SessionStore}: the harness `Session` plus its resolved id. */
|
|
13
48
|
export interface AcquiredSession {
|
|
14
49
|
session: Session;
|
|
@@ -66,13 +101,47 @@ export interface SessionStore {
|
|
|
66
101
|
* fact.
|
|
67
102
|
*/
|
|
68
103
|
readonly durability?: import("./checkpoint-store.js").StoreDurability;
|
|
104
|
+
/**
|
|
105
|
+
* Subagent transcript persistence — the store's PER-PLACEMENT durability declaration: "this store
|
|
106
|
+
* understands and honors placement semantics (partitioning + the obligations below), and sessions
|
|
107
|
+
* placed into the `subagent` partition have THIS durability". Declaration, never duck-typing (the
|
|
108
|
+
* wiring manifest relays it verbatim). The store-level {@link durability} continues to describe
|
|
109
|
+
* only UN-placed sessions — a routing/composite store whose transient host lane and durable
|
|
110
|
+
* subagent partition differ can therefore be honest on both axes. ABSENT = the store does not
|
|
111
|
+
* understand placement: an `acquire` carrying one is legally IGNORED (the session is then an
|
|
112
|
+
* ordinary one, and the deployment's delegation-durability tier reads as rows-only).
|
|
113
|
+
*
|
|
114
|
+
* Declaring this commits the store to the placement obligations:
|
|
115
|
+
* 1. a placed session enjoys the declared durability (`acquire(id, {requireExisting:true})`
|
|
116
|
+
* reaches it after a restart when `"durable"`);
|
|
117
|
+
* 2. placed sessions do NOT appear on host enumeration faces (`list()` and every deployment
|
|
118
|
+
* listing path — restart-stable);
|
|
119
|
+
* 3. acquire semantics for sessions WITHOUT a placement are byte-unchanged;
|
|
120
|
+
* 4. `release(placedId)` is a REAL deletion (durable history included) — the joint reap's
|
|
121
|
+
* "row and session both gone" depends on it;
|
|
122
|
+
* 5. placement is FIRST-WRITE IMMUTABLE — a re-open (`requireExisting`, with or without a
|
|
123
|
+
* placement argument) returns the same session and never rewrites the tuple;
|
|
124
|
+
* 6. a placed id cannot be CLAIMED: a later claim-form acquire (no `requireExisting`, no
|
|
125
|
+
* creation placement) of an existing placed id refuses loudly
|
|
126
|
+
* (`SessionError("placement_refused")`) instead of attaching.
|
|
127
|
+
*/
|
|
128
|
+
readonly placements?: {
|
|
129
|
+
subagent?: {
|
|
130
|
+
durability: "durable" | "process-local";
|
|
131
|
+
};
|
|
132
|
+
};
|
|
69
133
|
/** Get an existing session by id, or create one (optionally with a caller-supplied id). design/114 Phase3:
|
|
70
134
|
* `opts.requireExisting` ⇒ a store MUST fail loud (throw a `not_found` {@link SessionError}) on a genuinely
|
|
71
135
|
* missing id rather than create-on-miss — so a reuse-style warm-resume of a gone session errors instead of
|
|
72
136
|
* silently starting a fresh empty run. A store that cannot honor it MUST still not silently create (either
|
|
73
|
-
* implement the check or reject the option).
|
|
137
|
+
* implement the check or reject the option).
|
|
138
|
+
* `opts.placement` (subagent transcript persistence): CREATE the session into the named placement
|
|
139
|
+
* partition (see {@link SessionPlacement} — trusted-internals channel only). Ignored by stores
|
|
140
|
+
* that do not declare {@link placements}; on a store that does, the creating call is exempt from
|
|
141
|
+
* obligation 6 above and idempotent re-acquire by the same trusted chain stays legal. */
|
|
74
142
|
acquire(sessionId?: string, opts?: {
|
|
75
143
|
requireExisting?: boolean;
|
|
144
|
+
placement?: SessionPlacement;
|
|
76
145
|
}): Promise<AcquiredSession>;
|
|
77
146
|
/** Mark a session recently active (resets idle TTL where applicable). May be async for durable stores. */
|
|
78
147
|
touch(sessionId: string): void | Promise<void>;
|
|
@@ -115,6 +184,19 @@ export interface SessionStore {
|
|
|
115
184
|
* that cannot enumerate omits this (the shell then has no session-list affordance — honest degrade).
|
|
116
185
|
*/
|
|
117
186
|
list?(): Promise<SessionStoreSummary[]>;
|
|
187
|
+
/**
|
|
188
|
+
* Subagent transcript persistence — enumerate the PLACED partition (the input to the joint reap's
|
|
189
|
+
* partition leg: sessions whose owning agent row is gone are the orphan shape it collects).
|
|
190
|
+
* **Optional**: absent = the partition-GC leg is simply unavailable on this store (documented,
|
|
191
|
+
* fail-closed — placed sessions then age out only through the row-joined reap path). `olderThanMs`
|
|
192
|
+
* filters by session AGE (last activity where knowable, else `placedAt`); `scope` narrows to one
|
|
193
|
+
* tenant. Rows return the FULL `(scope, handle)` join tuple or the honest `tupleIncomplete`
|
|
194
|
+
* variant (see {@link PlacedSessionRow}) — consumers must skip incomplete tuples, never guess.
|
|
195
|
+
*/
|
|
196
|
+
listPlaced?(kind: "subagent", opts?: {
|
|
197
|
+
olderThanMs?: number;
|
|
198
|
+
scope?: string;
|
|
199
|
+
}): Promise<PlacedSessionRow[]>;
|
|
118
200
|
/**
|
|
119
201
|
* design/110 — FORK a session: copy `sourceId`'s durable history (root→leaf) into a NEW session and return its
|
|
120
202
|
* id (or `null` if the source doesn't exist). The forked session is independent (writes to it don't touch the
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { type BackgroundAgentRecord, type BackgroundAgentStore } from "./background-agent-store.js";
|
|
2
2
|
import { type StopSource, type TaskAccess, type TaskRetrievalStatus, type UnifiedTaskOutput, type UnifiedTaskResult, type BackgroundAgentTaskHandle, type DurableAgentCore, type ParkedClaimTicket, type RegisterBackgroundAgentInput } from "./task-registry-shared.js";
|
|
3
3
|
import { type ToolResultStore } from "./tool-result-store.js";
|
|
4
|
+
/**
|
|
5
|
+
* Subagent transcript persistence (delegation entry caps) — the ACTIVE handles of one delegation
|
|
6
|
+
* tree in THIS process's registry: `background_agent` handles whose status is running/pending
|
|
7
|
+
* (parked does not burn a concurrency slot — a suspension is not concurrency; terminal handles are
|
|
8
|
+
* not active) under the `(scope, rootSessionId)` key (full depth: a grandchild's row carries the
|
|
9
|
+
* tree's fixed-point root, so the whole tree shares one pool; a depth-1 row whose rootSessionId
|
|
10
|
+
* predates the floor falls back to its parentSessionId — same fallback the access predicate's root
|
|
11
|
+
* arm rides). SYNCHRONOUS by contract: the caps enforcement runs check→register in one synchronous
|
|
12
|
+
* segment, which is what makes the reservation race-free in-process.
|
|
13
|
+
*/
|
|
14
|
+
export declare function activeDelegationHandlesLane(core: DurableAgentCore, scope: string, rootSessionId: string): string[];
|
|
4
15
|
export declare function ensureDurableHeartbeatLane(core: DurableAgentCore): void;
|
|
5
16
|
/** design/151 S1a — enqueue one durable-row write (see {@link DurableAgentLane} for the lane
|
|
6
17
|
* contract). `patch` is captured at CALL time (the settle-site values), applied in chain order.
|
|
@@ -66,9 +77,25 @@ export declare function endDurableClaimLane(core: DurableAgentCore, id: string):
|
|
|
66
77
|
* transcript lives on unanchored — the exact strand this orchestration exists to prevent. */
|
|
67
78
|
export declare function reapDurableAgentsLane(core: DurableAgentCore, scope: string, deps: {
|
|
68
79
|
store: BackgroundAgentStore;
|
|
80
|
+
/** `listPlaced` (subagent transcript persistence, optional): arms the PARTITION leg below —
|
|
81
|
+
* placed transcript sessions whose owning row is GONE (row never landed / deleted-but-release-
|
|
82
|
+
* failed) are the orphan shape only a session-side enumeration can find. Absent = that leg is
|
|
83
|
+
* unavailable (documented; the row-joined path still runs). */
|
|
69
84
|
sessions?: {
|
|
70
85
|
unpin?(sessionId: string): unknown;
|
|
71
86
|
release(sessionId: string): Promise<void> | void;
|
|
87
|
+
listPlaced?(kind: "subagent", opts?: {
|
|
88
|
+
olderThanMs?: number;
|
|
89
|
+
scope?: string;
|
|
90
|
+
}): Promise<Array<{
|
|
91
|
+
sessionId: string;
|
|
92
|
+
placedAt: number;
|
|
93
|
+
} & ({
|
|
94
|
+
scope: string;
|
|
95
|
+
handle: string;
|
|
96
|
+
} | {
|
|
97
|
+
tupleIncomplete: true;
|
|
98
|
+
})>>;
|
|
72
99
|
};
|
|
73
100
|
/** design/151 §7.7 (F-13) — the row's mailbox dies with the row: a WINNING delete also drops
|
|
74
101
|
* the (scope, handle) mailbox (advisory — a mailbox fault never blocks the reap; an orphaned
|
|
@@ -86,6 +113,7 @@ export declare function reapDurableAgentsLane(core: DurableAgentCore, scope: str
|
|
|
86
113
|
rowsReaped: number;
|
|
87
114
|
sessionsReleased: number;
|
|
88
115
|
skippedNoSessions: number;
|
|
116
|
+
orphanPlacedReleased: number;
|
|
89
117
|
}>;
|
|
90
118
|
/** codex 终审 C-4 half — after a probe-false RELEASE the row must stop claiming a transcript:
|
|
91
119
|
* the heartbeat's F-1 arm keeps re-driving a flush-failed lane, so a later successful flush
|
|
@@ -7,6 +7,23 @@ import { delimitUntrusted } from "./untrusted-text.js";
|
|
|
7
7
|
import { boundedRedactedSummary } from "./untrusted-egress.js";
|
|
8
8
|
import { mintCompletionId, commitCompletionIdIfEmpty, clipTaskOutput, assertOwnership, sleepPollStep, alreadyTerminalStopNote, canAccess, normalizeAgentName, closestName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR, } from "./task-registry-shared.js";
|
|
9
9
|
import { buildToolResultRef, OFFLOAD_TOOL_NAME, toolResultProvenanceOf } from "./tool-result-store.js";
|
|
10
|
+
export function activeDelegationHandlesLane(core, scope, rootSessionId) {
|
|
11
|
+
const out = [];
|
|
12
|
+
for (const h of core.handles.values()) {
|
|
13
|
+
if (h.type !== "background_agent")
|
|
14
|
+
continue;
|
|
15
|
+
if (h.status !== "running" && h.status !== "pending")
|
|
16
|
+
continue;
|
|
17
|
+
if (h.scope !== scope)
|
|
18
|
+
continue;
|
|
19
|
+
const bh = h;
|
|
20
|
+
const root = bh.rootSessionId ?? bh.parentSessionId ?? (bh.sessionScoped ? bh.owner : undefined);
|
|
21
|
+
if (root !== rootSessionId)
|
|
22
|
+
continue;
|
|
23
|
+
out.push(h.id);
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
10
27
|
export function ensureDurableHeartbeatLane(core) {
|
|
11
28
|
if (core.durableHeartbeatTimer !== undefined)
|
|
12
29
|
return;
|
|
@@ -170,7 +187,7 @@ export async function reapDurableAgentsLane(core, scope, deps, policy) {
|
|
|
170
187
|
await deps.store.reap(scope, now, { staleRunningMaxAgeMs: policy.staleRunningMaxAgeMs });
|
|
171
188
|
}
|
|
172
189
|
if (policy.maxAgeMs === undefined && policy.keep === undefined)
|
|
173
|
-
return { rowsReaped: 0, sessionsReleased: 0, skippedNoSessions: 0 };
|
|
190
|
+
return { rowsReaped: 0, sessionsReleased: 0, skippedNoSessions: 0, orphanPlacedReleased: 0 };
|
|
174
191
|
const terminal = (await deps.store.listByScope(scope)).filter((r) => r.status !== "running" && r.status !== "parked");
|
|
175
192
|
terminal.sort((a, b) => b.spawnedAt - a.spawnedAt);
|
|
176
193
|
const doomed = new Map();
|
|
@@ -239,7 +256,51 @@ export async function reapDurableAgentsLane(core, scope, deps, policy) {
|
|
|
239
256
|
core.reapedHandles.delete(r.handle);
|
|
240
257
|
}
|
|
241
258
|
}
|
|
242
|
-
|
|
259
|
+
let orphanPlacedReleased = 0;
|
|
260
|
+
if (policy.maxAgeMs !== undefined && deps.sessions !== undefined && typeof deps.sessions.listPlaced === "function") {
|
|
261
|
+
let placed = [];
|
|
262
|
+
try {
|
|
263
|
+
placed = await deps.sessions.listPlaced("subagent", { olderThanMs: policy.maxAgeMs, scope });
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
placed = [];
|
|
267
|
+
}
|
|
268
|
+
for (const p of placed) {
|
|
269
|
+
if ("tupleIncomplete" in p)
|
|
270
|
+
continue;
|
|
271
|
+
if (p.scope !== scope)
|
|
272
|
+
continue;
|
|
273
|
+
if (core.claimingHandles.has(p.handle) || core.reapingHandles.has(p.handle))
|
|
274
|
+
continue;
|
|
275
|
+
let row;
|
|
276
|
+
try {
|
|
277
|
+
row = await deps.store.get(p.handle, scope);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (row !== null)
|
|
283
|
+
continue;
|
|
284
|
+
const inProc = core.handles.get(p.handle);
|
|
285
|
+
if (inProc !== undefined && (inProc.status === "running" || inProc.status === "pending" || inProc.status === "parked"))
|
|
286
|
+
continue;
|
|
287
|
+
try {
|
|
288
|
+
await deps.sessions.unpin?.(p.sessionId);
|
|
289
|
+
await deps.sessions.release(p.sessionId);
|
|
290
|
+
orphanPlacedReleased++;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
}
|
|
294
|
+
if (deps.mailbox !== undefined) {
|
|
295
|
+
try {
|
|
296
|
+
await deps.mailbox.drop(scope, p.handle);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { rowsReaped, sessionsReleased, skippedNoSessions, orphanPlacedReleased };
|
|
243
304
|
}
|
|
244
305
|
export function releaseDurableTranscriptAnchorLane(core, id) {
|
|
245
306
|
const handle = core.handles.get(id);
|
|
@@ -166,9 +166,24 @@ export declare class TaskRegistry {
|
|
|
166
166
|
endDurableClaim(id: string): void;
|
|
167
167
|
reapDurableAgents(scope: string, deps: {
|
|
168
168
|
store: BackgroundAgentStore;
|
|
169
|
+
/** `listPlaced` (subagent transcript persistence, optional) arms the partition-orphan leg —
|
|
170
|
+
* see {@link reapDurableAgentsLane}. Pass the deployment's SessionStore directly (the
|
|
171
|
+
* TtlSessionStore over a placement-capable repo exposes it). */
|
|
169
172
|
sessions?: {
|
|
170
173
|
unpin?(sessionId: string): unknown;
|
|
171
174
|
release(sessionId: string): Promise<void> | void;
|
|
175
|
+
listPlaced?(kind: "subagent", opts?: {
|
|
176
|
+
olderThanMs?: number;
|
|
177
|
+
scope?: string;
|
|
178
|
+
}): Promise<Array<{
|
|
179
|
+
sessionId: string;
|
|
180
|
+
placedAt: number;
|
|
181
|
+
} & ({
|
|
182
|
+
scope: string;
|
|
183
|
+
handle: string;
|
|
184
|
+
} | {
|
|
185
|
+
tupleIncomplete: true;
|
|
186
|
+
})>>;
|
|
172
187
|
};
|
|
173
188
|
/** design/151 §7.7 (F-13) — the row's mailbox dies with the row: a WINNING delete also drops
|
|
174
189
|
* the (scope, handle) mailbox (advisory — a mailbox fault never blocks the reap; an orphaned
|
|
@@ -186,6 +201,7 @@ export declare class TaskRegistry {
|
|
|
186
201
|
rowsReaped: number;
|
|
187
202
|
sessionsReleased: number;
|
|
188
203
|
skippedNoSessions: number;
|
|
204
|
+
orphanPlacedReleased: number;
|
|
189
205
|
}>;
|
|
190
206
|
releaseDurableTranscriptAnchor(id: string): void;
|
|
191
207
|
bindBackgroundAgentSession(id: string, sessionId: string): void;
|
|
@@ -270,6 +286,11 @@ export declare class TaskRegistry {
|
|
|
270
286
|
/** #258 — the row's current stop-cycle counter, read by the spawn lanes right after registering to
|
|
271
287
|
* thread into the child's `RunInternals.cycleSeq`; see {@link backgroundAgentCycleSeqLane}. */
|
|
272
288
|
backgroundAgentCycleSeq(id: string): number | undefined;
|
|
289
|
+
/** Subagent transcript persistence (delegation entry caps) — one delegation tree's ACTIVE
|
|
290
|
+
* (running/pending) a* handles in THIS process, keyed `(scope, rootSessionId)`; SYNCHRONOUS by
|
|
291
|
+
* contract (the caps check and the registration form one atomic segment). See
|
|
292
|
+
* {@link activeDelegationHandlesLane}. */
|
|
293
|
+
activeDelegationHandles(scope: string, rootSessionId: string): string[];
|
|
273
294
|
/** ASYNC (revive arbitration) — the durable half is a guarded ownership claim on the row (awaited before the
|
|
274
295
|
* in-memory flip), so this leg and a cross-process claim arbitrate in one domain instead of both
|
|
275
296
|
* believing they own the cycle. See {@link reviveBackgroundAgentLane}. */
|
|
@@ -10,7 +10,7 @@ import { registerWorkflowLane, pollWorkflowLane, stopWorkflowLane } from "./task
|
|
|
10
10
|
import { mintCompletionId, canAccessWorkflowRun, formatWorkflowRun, clipTaskOutput, assertOwnership, sleepPollStep, statusFromBackground, rollSpoolText, accountDroppedBytes, renderSpoolBody, spoolDropNote, droppedGapNote, alreadyTerminalStopNote, terminalTaskSummary, TASK_OUTPUT_MAX_CHARS, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE, canAccess, DURABLE_AGENT_HANDLE_RE, } from "./task-registry-shared.js";
|
|
11
11
|
export { normalizeAgentName, DURABLE_AGENT_HEARTBEAT_MS, DURABLE_AGENT_HANDLE_RE, BG_AGENT_REAP_STOP_ERROR } from "./task-registry-shared.js";
|
|
12
12
|
import { TASK_OUTPUT_TOOL_NAME, TASK_STOP_TOOL_NAME, TASK_OUTPUT_CONTRACT, TASK_STOP_CONTRACT, TASK_OUTPUT_MISSING_ID_MESSAGE, TASK_STOP_MISSING_ID_MESSAGE, TASK_STOP_PARAMS, resolveTaskIdArg, REGISTRY_TASK_TOOL_CAPS, composeTaskOutputDescription, composeTaskOutputParams, composeTaskStopDescription, } from "./task-tool-shape.js";
|
|
13
|
-
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
13
|
+
import { durableAgentArmedLane, durableAgentRowProbeLane, beginDurableClaimLane, endDurableClaimLane, reapDurableAgentsLane, noteBackgroundAgentActivityLane, reapStaleSessionBackgroundAgentsLane, releaseDurableTranscriptAnchorLane, bindBackgroundAgentSessionLane, registerBackgroundAgentLane, recordBackgroundAgentOrgAdmissionLane, parkBackgroundAgentLane, reconcileParkedAgentsLane, claimParkedAgentLane, rollbackParkedClaimLane, consumeParkedFlipLane, finalizeParkedResumeLane, settleBackgroundAgentLane, abortBackgroundAgentsForOwnerLane, serveDurableAgentRowLane, resolveBackgroundAgentByNameLane, backgroundAgentCycleSeqLane, activeDelegationHandlesLane, markRetainedContinuationLane, reviveBackgroundAgentLane, settleRevivedAgentLane, unmarkRetainedContinuationLane, attachAgentNotifyLane, deliverToRunningAgentLane, runningBackgroundAgentLabelsLane, runningAgentFooterLane, notFoundRunningAgentsTail, pollBackgroundAgentLane, stopBackgroundAgentLane, } from "./task-registry-agent.js";
|
|
14
14
|
export { canAccessWorkflowRun, clipTaskOutput, MONITOR_BATCH_WINDOW_MS, MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, MONITOR_MAX_BATCHES_PER_MINUTE };
|
|
15
15
|
const BLOCK_DEFAULT_TIMEOUT_MS = 30_000;
|
|
16
16
|
const BLOCK_MAX_TIMEOUT_MS = 600_000;
|
|
@@ -194,6 +194,9 @@ export class TaskRegistry {
|
|
|
194
194
|
backgroundAgentCycleSeq(id) {
|
|
195
195
|
return backgroundAgentCycleSeqLane(this.core, id);
|
|
196
196
|
}
|
|
197
|
+
activeDelegationHandles(scope, rootSessionId) {
|
|
198
|
+
return activeDelegationHandlesLane(this.core, scope, rootSessionId);
|
|
199
|
+
}
|
|
197
200
|
reviveBackgroundAgent(id, access, abort) {
|
|
198
201
|
return reviveBackgroundAgentLane(this.core, id, access, abort);
|
|
199
202
|
}
|