@sema-agent/core 6.0.0 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/dist/agents/launch-receipt-contract.d.ts +34 -0
- package/dist/agents/launch-receipt-contract.js +5 -0
- package/dist/agents/subagent.d.ts +134 -2
- package/dist/agents/subagent.js +132 -31
- package/dist/core/file-history-store.js +24 -2
- package/dist/core/governance-codes.d.ts +11 -2
- package/dist/core/governance-codes.js +1 -0
- package/dist/core/permission-rule-consent.d.ts +42 -2
- package/dist/core/permission-rule-consent.js +93 -11
- package/dist/core/permission-rule-model.d.ts +51 -9
- package/dist/core/permission-rule-model.js +4 -2
- package/dist/core/permission-rule-session.d.ts +124 -0
- package/dist/core/permission-rule-session.js +121 -0
- package/dist/core/permission-rule-store.d.ts +65 -2
- package/dist/core/permission-rule-store.js +60 -6
- package/dist/core/permission-rule-sync.d.ts +9 -0
- package/dist/core/permission-rule-sync.js +37 -8
- package/dist/core/roles.js +1 -1
- package/dist/core/runner/prepare-task.js +35 -2
- package/dist/core/runner/runtask.js +2 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +15 -1
- package/dist/core/task-notification.d.ts +20 -0
- package/dist/core/trace.d.ts +7 -2
- package/dist/core/types.d.ts +85 -1
- package/dist/core/wiring-manifest.d.ts +18 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/orchestration/run-workflow-tool.js +2 -2
- package/dist/orchestration/workflow.js +18 -10
- package/dist/stores/file/permission-rule-store.d.ts +11 -0
- package/dist/stores/file/permission-rule-store.js +22 -9
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +15 -1
package/dist/agents/subagent.js
CHANGED
|
@@ -37,10 +37,27 @@ function delegatedCostField(stats) {
|
|
|
37
37
|
const total = rollupDelegatedCost(stats, stats.nested);
|
|
38
38
|
return total !== undefined ? { costMicroUsd: total } : {};
|
|
39
39
|
}
|
|
40
|
+
export function delegatedGranularityFields(stats, elapsedMs) {
|
|
41
|
+
return { ...(stats.toolCalls !== undefined ? { toolUses: stats.toolCalls } : {}), durationMs: elapsedMs };
|
|
42
|
+
}
|
|
43
|
+
function providerFaultField(child) {
|
|
44
|
+
if (child.apiFailure === undefined)
|
|
45
|
+
return {};
|
|
46
|
+
return {
|
|
47
|
+
apiFailure: {
|
|
48
|
+
...(typeof child.apiFailure.status === "number" ? { status: child.apiFailure.status } : {}),
|
|
49
|
+
...(typeof child.apiFailure.requestId === "string" ? { requestId: child.apiFailure.requestId } : {}),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export function delegatedGranularityFieldsWire(stats, elapsedMs) {
|
|
54
|
+
return { ...(stats.toolCalls !== undefined ? { tool_uses: stats.toolCalls } : {}), duration_ms: elapsedMs };
|
|
55
|
+
}
|
|
40
56
|
export { RETAIN_DEFAULT_TTL_MS, RETAIN_DEFAULT_MAX };
|
|
41
57
|
import { SUBAGENT_RESUME_CAP, SubagentRetainLedger, getOrCreateSessionRetainLedger, ensureSessionReapHook, createResumePrompt, } from "./retain-ledger.js";
|
|
42
58
|
import { createPeerInboundChainRef, createPeerSelfRef } from "./peer-admission.js";
|
|
43
59
|
import { recordRosterSpawn } from "./roster-store.js";
|
|
60
|
+
import { LAUNCH_RECEIPT_OWN_WORDS_CLAUSE, launchReceiptNoQuoteClause } from "./launch-receipt-contract.js";
|
|
44
61
|
import { ObserverDigestTap, ObserverPairing, createObserverReportToolSpec, markObserverTaskId, unmarkObserverTaskId, isObserverTaskId, ObserverResumeStateError, ObserverStoppedByUserError, observerFramingPrompt, observerSlug, resolveObserverDeclaration, } from "./observer.js";
|
|
45
62
|
import { SubagentStepRecorder } from "./subagent-steps.js";
|
|
46
63
|
const BG_AGENT_RESULT_MAX = 4_000;
|
|
@@ -326,6 +343,7 @@ function resumabilityClaim(f) {
|
|
|
326
343
|
const BG_AGENT_COLLATERAL_REAP_REASON = "its parent run ended";
|
|
327
344
|
const RESERVED_AGENT_NAMES = new Set([OUTPUT_TOOL_NAME, TOOL_SEARCH_NAME, OFFLOAD_TOOL_NAME, REPORT_BLOCKED_TOOL_NAME, DEFAULT_SUBAGENT_TOOL_NAME]);
|
|
328
345
|
export const REPORT_FIELD_MAX = 300;
|
|
346
|
+
const ROSTER_ECHO_MAX = 400;
|
|
329
347
|
function configError(message, code) {
|
|
330
348
|
const e = new Error(message);
|
|
331
349
|
e.code = code;
|
|
@@ -364,6 +382,7 @@ export function completedAgentCard(child, extras) {
|
|
|
364
382
|
...(child.blockedReason !== undefined ? { blockedReason: child.blockedReason } : {}),
|
|
365
383
|
...(child.errorMessage !== undefined ? { errorMessage: child.errorMessage } : {}),
|
|
366
384
|
...(child.errorCode !== undefined ? { errorCode: child.errorCode } : {}),
|
|
385
|
+
...providerFaultField(child),
|
|
367
386
|
...(child.retryAfterMs !== undefined ? { retryAfterMs: child.retryAfterMs } : {}),
|
|
368
387
|
...(child.degraded !== undefined ? { degraded: child.degraded } : {}),
|
|
369
388
|
...(extras.handbackWarning !== undefined ? { handbackWarning: extras.handbackWarning } : {}),
|
|
@@ -711,9 +730,10 @@ export function createSubagentResume(deps) {
|
|
|
711
730
|
const completionIdRevive = deps.registry?.getCompletionId(deps.taskId ?? "");
|
|
712
731
|
const reviveName = deps.rowDescription ?? `sub-agent ${marker}`;
|
|
713
732
|
const failReasonRevive = status === "failed" ? child.errorMessage : undefined;
|
|
733
|
+
const reviveElapsedMs = Date.now() - reviveStartedAt;
|
|
714
734
|
const reviveTerminalSummary = failReasonRevive !== undefined
|
|
715
|
-
? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(
|
|
716
|
-
: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(
|
|
735
|
+
? `Agent "${reviveName}" (resumed) failed: ${failReasonRevive}${ccElapsedTag(reviveElapsedMs)}`.slice(0, 300) + errorKindClause(errClassRevive)
|
|
736
|
+
: `Agent "${reviveName}" (resumed) ${status === "killed" ? "stopped" : child.status === "completed" ? "finished" : String(child.status)}${ccElapsedTag(reviveElapsedMs)}`;
|
|
717
737
|
const reviveDurableProbe = deps.registry !== undefined && deps.taskId !== undefined ? deps.registry.durableAgentRowProbe(deps.taskId) : undefined;
|
|
718
738
|
const reviveDurableRow = reviveDurableProbe !== undefined ? await reviveDurableProbe().catch(() => false) : false;
|
|
719
739
|
const resumableRevive = resumabilityClaim({
|
|
@@ -739,7 +759,7 @@ export function createSubagentResume(deps) {
|
|
|
739
759
|
summary: reviveTerminalSummary,
|
|
740
760
|
...resumeResidual(),
|
|
741
761
|
resumable: resumableRevive,
|
|
742
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
762
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, reviveElapsedMs) },
|
|
743
763
|
...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
|
|
744
764
|
});
|
|
745
765
|
const resumeFrame = {
|
|
@@ -753,11 +773,12 @@ export function createSubagentResume(deps) {
|
|
|
753
773
|
summary: reviveTerminalSummary,
|
|
754
774
|
...(failReasonRevive !== undefined ? { error: failReasonRevive.slice(0, REPORT_FIELD_MAX) } : {}),
|
|
755
775
|
...(status === "failed" && errCodeRevive !== undefined ? { errorCode: errCodeRevive } : {}),
|
|
776
|
+
...providerFaultField(child),
|
|
756
777
|
...(resumeHandbackResult ? { result: notifyResultField(resumeHandbackResult) } : {}),
|
|
757
778
|
...(status === "killed" && child.result ? { partial: true } : {}),
|
|
758
779
|
...resumeResidual(),
|
|
759
780
|
resumable: resumableRevive,
|
|
760
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
781
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, reviveElapsedMs) },
|
|
761
782
|
...(completionIdRevive !== undefined ? { completionId: completionIdRevive } : {}),
|
|
762
783
|
};
|
|
763
784
|
const gateOwner = ownsTerminalFacesRevive && status === "completed" && deps.notify !== undefined && deps.registry !== undefined && ledger.get(deps.parentToolCallId) === entry
|
|
@@ -1206,7 +1227,7 @@ export function forkWorktreeTranslationNote(parentCwd, worktreeDir) {
|
|
|
1206
1227
|
export function asyncLaunchedReceipt(p) {
|
|
1207
1228
|
const noteLines = (p.notes ?? []).filter((n) => n !== undefined && n !== "");
|
|
1208
1229
|
const oneShotBlocked = p.notify && p.oneShot === true;
|
|
1209
|
-
return (`Async agent launched successfully. (
|
|
1230
|
+
return (`Async agent launched successfully. (${launchReceiptNoQuoteClause(", including the task_id below,")})\n` +
|
|
1210
1231
|
`task_id: ${p.taskId} (internal ID - do not mention to user. Use SendMessage with to: '${p.taskId}', summary: '<5-10 word recap>' to continue this agent.)\n` +
|
|
1211
1232
|
(oneShotBlocked
|
|
1212
1233
|
? `${p.workingLine} This is a ONE-SHOT submission — there is no later turn for a background notification to land in, so do NOT end your turn expecting one. Actively wait instead: TaskOutput(task_id: "${p.taskId}", block: true). If it is still running after the wait, wait again (bounded) rather than ending the turn, or write out your best available answer now if you are near your own time budget.\n`
|
|
@@ -1215,10 +1236,18 @@ export function asyncLaunchedReceipt(p) {
|
|
|
1215
1236
|
: `${p.workingLine} Its result is NOT pushed automatically — retrieve progress and results with TaskOutput(task_id) where mounted.\n`) +
|
|
1216
1237
|
noteLines.map((n) => `${n}\n`).join("") +
|
|
1217
1238
|
(oneShotBlocked
|
|
1218
|
-
?
|
|
1239
|
+
? `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} Do not assume a later message will deliver the result: this submission ends after this turn, so retrieve it now with the blocking TaskOutput wait above before writing your final answer.`
|
|
1219
1240
|
: p.notify
|
|
1220
|
-
?
|
|
1221
|
-
:
|
|
1241
|
+
? `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} Agent results will arrive in a subsequent message. If the user asks for progress, say the agent is still running.`
|
|
1242
|
+
: `${LAUNCH_RECEIPT_OWN_WORDS_CLAUSE} The result will NOT arrive on its own — retrieve it with TaskOutput(task_id) where mounted before relying on it.`));
|
|
1243
|
+
}
|
|
1244
|
+
function runnerModelCatalog(runner) {
|
|
1245
|
+
try {
|
|
1246
|
+
return runner?.agentCatalog?.models;
|
|
1247
|
+
}
|
|
1248
|
+
catch {
|
|
1249
|
+
return undefined;
|
|
1250
|
+
}
|
|
1222
1251
|
}
|
|
1223
1252
|
export function createSubagentTool(opts) {
|
|
1224
1253
|
const catalog = opts.runner.agentCatalog;
|
|
@@ -1227,8 +1256,7 @@ export function createSubagentTool(opts) {
|
|
|
1227
1256
|
if (opts.builtinAgents === undefined && catalog?.builtinAgents !== undefined) {
|
|
1228
1257
|
opts = { ...opts, builtinAgents: catalog.builtinAgents };
|
|
1229
1258
|
}
|
|
1230
|
-
|
|
1231
|
-
opts = { ...opts, models: catalog.models };
|
|
1259
|
+
const assemblyModels = opts.models ?? catalog?.models;
|
|
1232
1260
|
if (opts.agents?.length) {
|
|
1233
1261
|
opts = {
|
|
1234
1262
|
...opts,
|
|
@@ -1252,7 +1280,7 @@ export function createSubagentTool(opts) {
|
|
|
1252
1280
|
}
|
|
1253
1281
|
if (typeof a.model === "string") {
|
|
1254
1282
|
try {
|
|
1255
|
-
resolveModel(a.model,
|
|
1283
|
+
resolveModel(a.model, assemblyModels);
|
|
1256
1284
|
}
|
|
1257
1285
|
catch {
|
|
1258
1286
|
throw configError(`createSubagentTool: agent "${a.name}" references unknown model "${a.model}" (pass \`models\` to resolve string refs).`, "config.agent.unknown_model");
|
|
@@ -1308,7 +1336,9 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1308
1336
|
...(forkOffered ? [FORK_SUBAGENT_TYPE] : []),
|
|
1309
1337
|
...(generalPurposeShadowed ? [] : [GENERAL_PURPOSE_SUBAGENT_TYPE]),
|
|
1310
1338
|
];
|
|
1311
|
-
const
|
|
1339
|
+
const activeModels = () => opts.models ?? runnerModelCatalog(opts.runner);
|
|
1340
|
+
const mountModels = activeModels();
|
|
1341
|
+
const rosterNames = mountModels ? Object.keys(mountModels) : undefined;
|
|
1312
1342
|
const agentListing = [
|
|
1313
1343
|
...(generalPurposeShadowed
|
|
1314
1344
|
? []
|
|
@@ -1424,7 +1454,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1424
1454
|
})),
|
|
1425
1455
|
model: Type.Optional(Type.String({
|
|
1426
1456
|
description: rosterNames !== undefined && rosterNames.length > 0
|
|
1427
|
-
? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model, or inherits from the parent. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}" — forks always inherit the parent model.`
|
|
1457
|
+
? `Optional model override for this agent. Takes precedence over the agent definition's model. If omitted, uses the agent definition's model, or inherits from the parent. A value outside this deployment's model catalog is REFUSED — the call does not fall back silently. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}" — forks always inherit the parent model.`
|
|
1428
1458
|
: `Optional model override request. No model catalog is configured here, so a requested name cannot be resolved — the agent runs its definition's model (or inherits from the parent) and the result notes the override was not applied. Ignored for subagent_type: "${FORK_SUBAGENT_TYPE}".`,
|
|
1429
1459
|
})),
|
|
1430
1460
|
isolation: Type.Optional(Type.Literal("worktree", {
|
|
@@ -1560,23 +1590,54 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1560
1590
|
};
|
|
1561
1591
|
}
|
|
1562
1592
|
}
|
|
1593
|
+
if (typeof a.model === "string" && a.model.trim() === "" && !wantsFork && reviveClaim === undefined) {
|
|
1594
|
+
return {
|
|
1595
|
+
isError: true,
|
|
1596
|
+
content: "Sub-agent not started: `model` was empty. Name a model from this deployment's catalog, or omit the parameter to run the agent type's own model.",
|
|
1597
|
+
details: { error: "model.empty" },
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1563
1600
|
const requestedModel = typeof a.model === "string" && a.model.trim() !== "" ? a.model.trim() : undefined;
|
|
1564
1601
|
let perCallModel;
|
|
1565
1602
|
let modelNote;
|
|
1566
|
-
|
|
1603
|
+
const judgedModels = activeModels();
|
|
1604
|
+
const rosterKeys = judgedModels !== undefined ? Object.keys(judgedModels) : [];
|
|
1605
|
+
if (typeof a.model === "string" && requestedModel === undefined) {
|
|
1606
|
+
modelNote = wantsFork
|
|
1607
|
+
? `note: model "" was ignored — a fork always runs on the caller's model.`
|
|
1608
|
+
: `note: the durable record's model was blank and was NOT applied — the revived agent ran on its default (inherited) model.`;
|
|
1609
|
+
}
|
|
1610
|
+
else if (requestedModel !== undefined) {
|
|
1567
1611
|
if (wantsFork) {
|
|
1568
1612
|
modelNote = `note: model "${requestedModel}" was ignored — a fork always runs on the caller's model.`;
|
|
1569
1613
|
}
|
|
1570
|
-
else if (
|
|
1614
|
+
else if (rosterKeys.length === 0) {
|
|
1571
1615
|
modelNote = `note: model "${requestedModel}" was NOT applied — this delegation tool has no model roster configured; the sub-agent ran on its default (inherited) model.`;
|
|
1572
1616
|
}
|
|
1573
1617
|
else {
|
|
1574
1618
|
try {
|
|
1575
|
-
resolveModel(requestedModel,
|
|
1576
|
-
perCallModel = requestedModel;
|
|
1619
|
+
perCallModel = resolveModel(requestedModel, judgedModels);
|
|
1577
1620
|
}
|
|
1578
1621
|
catch {
|
|
1579
|
-
|
|
1622
|
+
if (reviveClaim !== undefined) {
|
|
1623
|
+
modelNote = `note: recorded model "${inlineUntrusted(requestedModel, 80)}" is not on the current model roster and was NOT applied — the revived agent ran on its default (inherited) model.`;
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
const shown = [];
|
|
1627
|
+
let budget = ROSTER_ECHO_MAX;
|
|
1628
|
+
for (const k of rosterKeys) {
|
|
1629
|
+
if (budget - k.length < 0)
|
|
1630
|
+
break;
|
|
1631
|
+
shown.push(k);
|
|
1632
|
+
budget -= k.length + 2;
|
|
1633
|
+
}
|
|
1634
|
+
const rosterEcho = shown.length === rosterKeys.length ? shown.join(", ") : `${shown.join(", ")}, … (${rosterKeys.length - shown.length} more)`;
|
|
1635
|
+
return {
|
|
1636
|
+
isError: true,
|
|
1637
|
+
content: `Sub-agent not started: unknown model "${inlineUntrusted(requestedModel, 80)}". Available model values: ${inlineUntrusted(rosterEcho, ROSTER_ECHO_MAX + 32)}. Omit \`model\` to run the agent type's own model, or inherit the caller's.`,
|
|
1638
|
+
details: { error: "model.unknown" },
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1580
1641
|
}
|
|
1581
1642
|
}
|
|
1582
1643
|
}
|
|
@@ -1690,9 +1751,39 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
1690
1751
|
const childTools = def
|
|
1691
1752
|
? resolveToolSubset(childPool, def.allowTools, def.denyTools)
|
|
1692
1753
|
: resolveToolSubset(childPool, opts.allowTools, opts.denyTools);
|
|
1693
|
-
|
|
1754
|
+
let childModel = wantsFork
|
|
1694
1755
|
? ctx.model ?? opts.model
|
|
1695
1756
|
: perCallModel ?? (def ? (def.model ?? ctx.model ?? opts.model) : opts.model);
|
|
1757
|
+
if (reviveClaim !== undefined && typeof childModel === "string") {
|
|
1758
|
+
const resolvable = (ref) => {
|
|
1759
|
+
try {
|
|
1760
|
+
resolveModel(ref, runnerModelCatalog(opts.runner));
|
|
1761
|
+
return true;
|
|
1762
|
+
}
|
|
1763
|
+
catch {
|
|
1764
|
+
return false;
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
if (!resolvable(childModel)) {
|
|
1768
|
+
const dead = childModel;
|
|
1769
|
+
const rest = def !== undefined ? [ctx.model, opts.model] : [opts.model];
|
|
1770
|
+
childModel = rest.find((c) => c !== undefined && (typeof c !== "string" || resolvable(c)));
|
|
1771
|
+
if (modelNote === undefined) {
|
|
1772
|
+
modelNote = `note: model "${inlineUntrusted(dead, 80)}" from this agent type's configuration is not on the current model roster and was NOT applied — the revived agent ran on its default (inherited) model.`;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
const spawnModel = typeof childModel === "string"
|
|
1777
|
+
? (() => {
|
|
1778
|
+
try {
|
|
1779
|
+
return resolveModel(childModel, judgedModels).id;
|
|
1780
|
+
}
|
|
1781
|
+
catch {
|
|
1782
|
+
return resolveModelDisplayLabel(childModel);
|
|
1783
|
+
}
|
|
1784
|
+
})()
|
|
1785
|
+
: childModel?.id;
|
|
1786
|
+
const rowModelKey = perCallModel !== undefined ? requestedModel : typeof childModel === "string" ? childModel : childModel?.id;
|
|
1696
1787
|
const childSystemPrompt = def?.systemPrompt ?? opts.systemPrompt;
|
|
1697
1788
|
const childDefaultPersona = childSystemPrompt === undefined && !wantsFork ? SUBAGENT_PROMPT : undefined;
|
|
1698
1789
|
const defMaxTurns = typeof def?.maxTurns === "number" && Number.isFinite(def.maxTurns) && def.maxTurns > 0 ? def.maxTurns : undefined;
|
|
@@ -2342,7 +2433,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2342
2433
|
...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
|
|
2343
2434
|
...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
|
|
2344
2435
|
...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
|
|
2345
|
-
...(
|
|
2436
|
+
...(rowModelKey !== undefined ? { model: rowModelKey } : {}),
|
|
2346
2437
|
deliveryChannel: "attaching",
|
|
2347
2438
|
toolUseId: ctx.toolCallId,
|
|
2348
2439
|
...(bg.agentStore !== undefined ? { store: bg.agentStore } : {}),
|
|
@@ -2403,7 +2494,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2403
2494
|
}, { priority: "next" }), "subagent.reapTerminalNotify");
|
|
2404
2495
|
});
|
|
2405
2496
|
if (agentName !== undefined) {
|
|
2406
|
-
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(
|
|
2497
|
+
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, sessionId: forkedId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(rowModelKey !== undefined ? { model: rowModelKey } : {}), ...(modelFallback !== undefined ? { modelFallback } : {}), ...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
2407
2498
|
}
|
|
2408
2499
|
const bgSink = ctx.onBackgroundChildEvent;
|
|
2409
2500
|
const sinkEmit = (event) => {
|
|
@@ -2422,6 +2513,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2422
2513
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2423
2514
|
description: shortDesc,
|
|
2424
2515
|
agentType: spawnAgentType,
|
|
2516
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
2425
2517
|
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2426
2518
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2427
2519
|
sessionId: forkedId,
|
|
@@ -2451,6 +2543,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2451
2543
|
parentToolCallId: ctx.toolCallId,
|
|
2452
2544
|
progressTaskId: forkedId,
|
|
2453
2545
|
agentType: spawnAgentType,
|
|
2546
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
2454
2547
|
...(bgForkCycleSeq !== undefined ? { seq: bgForkCycleSeq } : {}),
|
|
2455
2548
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2456
2549
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -2485,6 +2578,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2485
2578
|
progressTaskId: e.taskId,
|
|
2486
2579
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
2487
2580
|
agentType: spawnAgentType,
|
|
2581
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
2488
2582
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
2489
2583
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
2490
2584
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -2562,9 +2656,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2562
2656
|
const resumableFork = false;
|
|
2563
2657
|
const forkTranscriptId = child.sessionId ?? forkedId;
|
|
2564
2658
|
const failReasonFork = settledBg === "failed" ? child.errorMessage : undefined;
|
|
2659
|
+
const forkElapsedMs = Date.now() - forkBgStartedAt;
|
|
2565
2660
|
const forkTerminalSummary = failReasonFork !== undefined
|
|
2566
|
-
? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(
|
|
2567
|
-
: `${ccCompletionText(shortDesc, settledBg, String(child.status),
|
|
2661
|
+
? `Agent "${shortDesc}" failed: ${failReasonFork}${ccElapsedTag(forkElapsedMs)}`.slice(0, 300) + errorKindClause(errClassFork)
|
|
2662
|
+
: `${ccCompletionText(shortDesc, settledBg, String(child.status), forkElapsedMs)}`;
|
|
2568
2663
|
const ownsTerminalFacesFork = bg.registry.claimAgentTerminalNotify(taskId);
|
|
2569
2664
|
if (ownsTerminalFacesFork)
|
|
2570
2665
|
sinkEmit({
|
|
@@ -2580,7 +2675,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2580
2675
|
summary: forkTerminalSummary,
|
|
2581
2676
|
...residualFork,
|
|
2582
2677
|
resumable: resumableFork,
|
|
2583
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
2678
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, forkElapsedMs) },
|
|
2584
2679
|
...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
|
|
2585
2680
|
});
|
|
2586
2681
|
try {
|
|
@@ -2594,12 +2689,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2594
2689
|
summary: forkTerminalSummary,
|
|
2595
2690
|
...(failReasonFork !== undefined ? { error: failReasonFork.slice(0, REPORT_FIELD_MAX) } : {}),
|
|
2596
2691
|
...(settledBg === "failed" && errCodeFork !== undefined ? { errorCode: errCodeFork } : {}),
|
|
2692
|
+
...providerFaultField(child),
|
|
2597
2693
|
...(child.sessionId ? { sessionId: child.sessionId } : {}),
|
|
2598
2694
|
...(forkBgResult ? { result: notifyResultField(forkBgResult) } : {}),
|
|
2599
2695
|
...(settledBg === "killed" && child.result ? { partial: true } : {}),
|
|
2600
2696
|
...residualFork,
|
|
2601
2697
|
resumable: resumableFork,
|
|
2602
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
2698
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, forkElapsedMs) },
|
|
2603
2699
|
...(completionIdFork !== undefined ? { completionId: completionIdFork } : {}),
|
|
2604
2700
|
}, { priority: "next" });
|
|
2605
2701
|
}
|
|
@@ -2912,7 +3008,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2912
3008
|
...(ctx.taskId !== undefined && ctx.taskId !== ctx.sessionId ? { parentTaskId: ctx.taskId } : {}),
|
|
2913
3009
|
...(ctx.sessionId !== undefined ? { parentSessionId: ctx.sessionId } : {}),
|
|
2914
3010
|
...((ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}),
|
|
2915
|
-
...(
|
|
3011
|
+
...(rowModelKey !== undefined ? { model: rowModelKey } : {}),
|
|
2916
3012
|
deliveryChannel: "attaching",
|
|
2917
3013
|
toolUseId: ctx.toolCallId,
|
|
2918
3014
|
...(bg.agentStore !== undefined ? { store: bg.agentStore } : {}),
|
|
@@ -2936,7 +3032,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2936
3032
|
chargeDelegationEntryHandle(bg.registry, capLedgerKey, bg.agentStore, reviveRow === undefined ? taskId : undefined);
|
|
2937
3033
|
childInternals.peerSelfRef?.addAxis("h", taskId);
|
|
2938
3034
|
if (agentName !== undefined) {
|
|
2939
|
-
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(
|
|
3035
|
+
recordRosterSpawn(ctx.roster, { name: agentName, agentId: taskId, toolUseId: ctx.toolCallId, owner: bgOwner, scope: bgScope, ...(reviveRow !== undefined ? (reviveRow.model !== undefined ? { model: reviveRow.model } : {}) : rowModelKey !== undefined ? { model: rowModelKey } : {}), ...(modelFallback !== undefined ? { modelFallback } : {}), ...(reviveRow !== undefined ? ((reviveRow.rootSessionId ?? reviveRow.parentSessionId) !== undefined ? { rootSessionId: reviveRow.rootSessionId ?? reviveRow.parentSessionId } : {}) : (ctx.rootSessionId ?? ctx.sessionId) !== undefined ? { rootSessionId: ctx.rootSessionId ?? ctx.sessionId } : {}), ...(sessionScopedBg ? { sessionScoped: true } : {}), createdAt: reviveRow?.spawnedAt ?? Date.now() }, (err) => opts.onObserverError?.(err, { site: "roster.recordSpawn" }));
|
|
2940
3036
|
}
|
|
2941
3037
|
const bgSink = ctx.onBackgroundChildEvent;
|
|
2942
3038
|
const sinkEmit = (event) => {
|
|
@@ -2992,6 +3088,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
2992
3088
|
...(bgScope !== undefined ? { scope: bgScope } : {}),
|
|
2993
3089
|
description: reviveRow !== undefined ? `${shortDesc} (revived)` : shortDesc,
|
|
2994
3090
|
agentType: spawnAgentType,
|
|
3091
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
2995
3092
|
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
2996
3093
|
...(agentName !== undefined ? { name: agentName } : {}),
|
|
2997
3094
|
sessionId: bgChildSessionId,
|
|
@@ -3065,6 +3162,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3065
3162
|
parentToolCallId: ctx.toolCallId,
|
|
3066
3163
|
progressTaskId: bgChildSessionId,
|
|
3067
3164
|
agentType: spawnAgentType,
|
|
3165
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
3068
3166
|
...(bgCycleSeq !== undefined ? { seq: bgCycleSeq } : {}),
|
|
3069
3167
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
3070
3168
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -3111,6 +3209,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3111
3209
|
progressTaskId: e.taskId,
|
|
3112
3210
|
...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
|
|
3113
3211
|
agentType: spawnAgentType,
|
|
3212
|
+
...(spawnModel !== undefined ? { model: spawnModel } : {}),
|
|
3114
3213
|
...(e.name !== undefined ? { name: e.name } : {}),
|
|
3115
3214
|
...(currentAction !== undefined ? { currentAction } : {}),
|
|
3116
3215
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
@@ -3399,9 +3498,10 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3399
3498
|
named: agentName !== undefined,
|
|
3400
3499
|
});
|
|
3401
3500
|
const failReasonBg = settled === "failed" ? unparkedPauseReason ?? child.errorMessage : undefined;
|
|
3501
|
+
const bgElapsedMs = Date.now() - bgStartedAt;
|
|
3402
3502
|
const bgTerminalSummary = failReasonBg !== undefined
|
|
3403
|
-
? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(
|
|
3404
|
-
: `${ccCompletionText(shortDesc, settled, String(child.status),
|
|
3503
|
+
? `Agent "${shortDesc}" failed: ${failReasonBg}${ccElapsedTag(bgElapsedMs)}${observerNote}`.slice(0, 300) + errorKindClause(errClassBg)
|
|
3504
|
+
: `${ccCompletionText(shortDesc, settled, String(child.status), bgElapsedMs)}${observerNote}`;
|
|
3405
3505
|
const ownsTerminalFaces = bg.registry.claimAgentTerminalNotify(taskId);
|
|
3406
3506
|
if (ownsTerminalFaces)
|
|
3407
3507
|
sinkEmit({
|
|
@@ -3416,7 +3516,7 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3416
3516
|
summary: bgTerminalSummary,
|
|
3417
3517
|
...residual,
|
|
3418
3518
|
resumable: resumableBg,
|
|
3419
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
3519
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFields(child.stats, bgElapsedMs) },
|
|
3420
3520
|
...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
|
|
3421
3521
|
});
|
|
3422
3522
|
const completionFrame = {
|
|
@@ -3429,12 +3529,13 @@ function createSubagentToolNode(opts, depth, excluded, extraToolsBudget) {
|
|
|
3429
3529
|
summary: bgTerminalSummary,
|
|
3430
3530
|
...(failReasonBg !== undefined ? { error: failReasonBg.slice(0, REPORT_FIELD_MAX) } : {}),
|
|
3431
3531
|
...(settled === "failed" && errCodeBg !== undefined ? { errorCode: errCodeBg } : {}),
|
|
3532
|
+
...providerFaultField(child),
|
|
3432
3533
|
...(child.sessionId ? { sessionId: child.sessionId } : {}),
|
|
3433
3534
|
...(bgHandbackResult ? { result: notifyResultField(bgHandbackResult) } : {}),
|
|
3434
3535
|
...(settled === "killed" && child.result ? { partial: true } : {}),
|
|
3435
3536
|
...residual,
|
|
3436
3537
|
resumable: resumableBg,
|
|
3437
|
-
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats) },
|
|
3538
|
+
usage: { tokens: child.stats.tokens, turns: child.stats.turns, ...delegatedCostField(child.stats), ...delegatedGranularityFieldsWire(child.stats, bgElapsedMs) },
|
|
3438
3539
|
...(completionIdBg !== undefined ? { completionId: completionIdBg } : {}),
|
|
3439
3540
|
};
|
|
3440
3541
|
const gateOwner = ownsTerminalFaces && settled === "completed" && notify !== undefined ? bgRetain?.childSessionId : undefined;
|
|
@@ -166,6 +166,14 @@ export function validateFileHistoryExport(data) {
|
|
|
166
166
|
return bad(`tracked row ${t.relOrAbsPath} carries a non-integer version number`);
|
|
167
167
|
if (v.blobHash !== null && (typeof v.blobHash !== "string" || !/^[0-9a-f]{64}$/.test(v.blobHash)))
|
|
168
168
|
return bad(`tracked row ${t.relOrAbsPath} carries a blob reference that is not a sha256 digest`);
|
|
169
|
+
if (v.mode !== undefined) {
|
|
170
|
+
if (typeof v.mode !== "number" || !Number.isInteger(v.mode) || v.mode < 0 || v.mode > 0o7777) {
|
|
171
|
+
return bad(`tracked row ${t.relOrAbsPath} carries a mode outside the permission-bit domain [0, 0o7777] — refused, never coerced`);
|
|
172
|
+
}
|
|
173
|
+
if ((v.mode & 0o6000) !== 0) {
|
|
174
|
+
return bad(`tracked row ${t.relOrAbsPath} carries a set-ID mode (0o${v.mode.toString(8)}) — a restore replaying setuid/setgid bits would mint a privilege-escalation primitive; refused`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
169
177
|
}
|
|
170
178
|
if (new Set(t.versions.map((v) => v.version)).size !== t.versions.length)
|
|
171
179
|
return bad(`tracked row ${t.relOrAbsPath} carries the same version number twice — a chain whose versions are ambiguous cannot be published`);
|
|
@@ -284,13 +292,15 @@ export async function applyFileRestoreViaEnv(env, absPath, target, opts) {
|
|
|
284
292
|
return { outcome: "identical" };
|
|
285
293
|
if (modeOnly === "applied")
|
|
286
294
|
return { outcome: "applied" };
|
|
295
|
+
if (modeOnly === "applied_unpreserved")
|
|
296
|
+
return { outcome: "applied", modeNotPreserved: true };
|
|
287
297
|
return { outcome: "identical", modeNotPreserved: true };
|
|
288
298
|
}
|
|
289
299
|
const w = await writeRestoredBytes(env, absPath, target.bytes, signal);
|
|
290
300
|
if (!w.ok)
|
|
291
301
|
return { outcome: w.refused ? "refused" : "failed", reason: `write: ${w.code}` };
|
|
292
302
|
const modeAfter = await applyRecordedMode(env, absPath, opts, undefined);
|
|
293
|
-
return { outcome: "applied", ...(modeAfter === "unpreserved" ? { modeNotPreserved: true } : {}) };
|
|
303
|
+
return { outcome: "applied", ...(modeAfter === "unpreserved" || modeAfter === "applied_unpreserved" ? { modeNotPreserved: true } : {}) };
|
|
294
304
|
}
|
|
295
305
|
async function restorePreflight(env, absPath, opts) {
|
|
296
306
|
const info = await env.fileInfo(absPath, opts.signal);
|
|
@@ -337,7 +347,10 @@ export async function previewFileDelta(env, absPath, targetHash, targetBytes, ta
|
|
|
337
347
|
return undefined;
|
|
338
348
|
const info = await env.fileInfo(absPath);
|
|
339
349
|
const currentMode = info.ok ? info.value.mode : undefined;
|
|
340
|
-
|
|
350
|
+
const replayMode = (targetMode & 0o6000) !== 0 ? targetMode & 0o1777 : targetMode;
|
|
351
|
+
return modeProvenDifferent(targetMode, currentMode) && modeProvenDifferent(replayMode, currentMode)
|
|
352
|
+
? { insertions: 0, deletions: 0 }
|
|
353
|
+
: undefined;
|
|
341
354
|
}
|
|
342
355
|
return countRestoreLineDiff(curBytes, targetBytes);
|
|
343
356
|
}
|
|
@@ -347,6 +360,15 @@ async function applyRecordedMode(env, absPath, opts, currentMode) {
|
|
|
347
360
|
return "unchanged";
|
|
348
361
|
if (currentMode === (want & 0o7777))
|
|
349
362
|
return "unchanged";
|
|
363
|
+
if ((want & 0o6000) !== 0) {
|
|
364
|
+
const stripped = want & 0o1777;
|
|
365
|
+
if (currentMode === stripped)
|
|
366
|
+
return "unpreserved";
|
|
367
|
+
if (env.setFileMode === undefined)
|
|
368
|
+
return "unpreserved";
|
|
369
|
+
const r = await env.setFileMode(absPath, stripped, opts.signal);
|
|
370
|
+
return r.ok ? "applied_unpreserved" : "unpreserved";
|
|
371
|
+
}
|
|
350
372
|
if (env.setFileMode === undefined)
|
|
351
373
|
return "unpreserved";
|
|
352
374
|
const r = await env.setFileMode(absPath, want & 0o7777, opts.signal);
|
|
@@ -62,12 +62,21 @@ export declare const RULE_SYNC_DROP_CODES: {
|
|
|
62
62
|
/** The server refused this local row (its response `dropped` names it); it leaves the live view so
|
|
63
63
|
* it does not ride — and get refused on — every future round. */
|
|
64
64
|
readonly server_rejected: "local-quarantined";
|
|
65
|
+
/** design/382 §4.3 — the row carries a SESSION scope, which the durable face (store, sync wire,
|
|
66
|
+
* at-rest bytes) structurally never holds: a session authorization lives in its session's overlay
|
|
67
|
+
* and dies with it. The row is dropped and disclosed, never landed and never quarantined (the
|
|
68
|
+
* quarantine area is itself durable — parking a session row there would persist it). Inbound-
|
|
69
|
+
* refused in every reachable case: the direct write arms refuse session scopes LOUDLY
|
|
70
|
+
* (`unsupported.session_scope_store`) before one can become local. */
|
|
71
|
+
readonly session_scope_not_durable: "inbound-refused";
|
|
65
72
|
};
|
|
66
73
|
/** Every reason a sync round may drop or quarantine a record. Closed set; free text is not a member. */
|
|
67
74
|
export type RuleSyncDropReason = keyof typeof RULE_SYNC_DROP_CODES;
|
|
68
75
|
/** The subset that may appear on a LOCAL quarantined row (design/182 §8.1 `quarantine` instruction /
|
|
69
|
-
* fence arm / local screening). `own_actor_forged` is inbound-only by construction
|
|
70
|
-
|
|
76
|
+
* fence arm / local screening). `own_actor_forged` is inbound-only by construction;
|
|
77
|
+
* `session_scope_not_durable` never quarantines — the quarantine area is durable, and a session row
|
|
78
|
+
* parked there would be a session row persisted (design/382 §4.3). */
|
|
79
|
+
export type RuleQuarantineReason = Exclude<RuleSyncDropReason, "own_actor_forged" | "session_scope_not_durable">;
|
|
71
80
|
/**
|
|
72
81
|
* WHO a notice code is for. `"user"` = a session-scoped disclosure the end user of that session is
|
|
73
82
|
* entitled to see (safe to project onto that session's event stream); `"operator"` = a
|
|
@@ -90,6 +90,7 @@ export const RULE_SYNC_DROP_CODES = {
|
|
|
90
90
|
dot_identity_conflict: "inbound-refused",
|
|
91
91
|
below_gc_frontier: "local-quarantined",
|
|
92
92
|
server_rejected: "local-quarantined",
|
|
93
|
+
session_scope_not_durable: "inbound-refused",
|
|
93
94
|
};
|
|
94
95
|
export const ENGINE_NOTICE_CODES = [
|
|
95
96
|
"config.autocompact_window_clamped",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
*/
|
|
30
30
|
import { type RuleOffer, type RuleRejectCode, type RuleScope, type RuleDot } from "./permission-rule-model.js";
|
|
31
31
|
import type { PermissionRuleStoreProvider, RuleOwner } from "./permission-rule-store.js";
|
|
32
|
+
import type { SessionRuleOverlay } from "./permission-rule-session.js";
|
|
32
33
|
/** One candidate rule inside an approval record: the exact text and where it would apply. */
|
|
33
34
|
export interface RuleCandidate {
|
|
34
35
|
rule: string;
|
|
@@ -199,6 +200,15 @@ export interface RuleConsentDeps {
|
|
|
199
200
|
* happened, and withholding the receipt helps no one.
|
|
200
201
|
*/
|
|
201
202
|
cardEdits?: boolean;
|
|
203
|
+
/**
|
|
204
|
+
* design/382 §4.3 — the SESSION-RULE OVERLAY: where a `{kind:"session"}` scoped candidate lands at
|
|
205
|
+
* redemption (the store write leg is never taken for one), and where the prepare-time coverage read
|
|
206
|
+
* merges a session's standing rows from. Host-provided, session-lifetime, never synced — see
|
|
207
|
+
* `permission-rule-session.ts` for the contract and the reference implementation. Absent ⇒ a
|
|
208
|
+
* session-scope candidate's redemption refuses loudly (there is nowhere for it to land), and
|
|
209
|
+
* coverage reads see no session rows — both fail toward asking.
|
|
210
|
+
*/
|
|
211
|
+
sessionRules?: SessionRuleOverlay;
|
|
202
212
|
}
|
|
203
213
|
/** In-memory approval records — the test backend and the reference CAS semantics, the stale-row
|
|
204
214
|
* envelope read included. */
|
|
@@ -257,8 +267,27 @@ export declare function prepareCardApproval(opts: {
|
|
|
257
267
|
/** The ask's call id and argument digest, recorded for reconciliation. */
|
|
258
268
|
toolCallId?: string;
|
|
259
269
|
boundInputHash?: string;
|
|
260
|
-
/**
|
|
270
|
+
/**
|
|
271
|
+
* Where a redeemed rule would apply — any of the three consent dimensions (design/382 §4.1).
|
|
272
|
+
*
|
|
273
|
+
* design/382 §4.4 (#490②, BREAKING B2) — the DEFAULT is no longer global:
|
|
274
|
+
* · present ⇒ used as given (the explicit channel; all three members legal, garbage refused loudly);
|
|
275
|
+
* · absent with `cwd` present ⇒ `{ kind: "project", root: cwd }`. Root = the adjudicated call's
|
|
276
|
+
* cwd is a deliberately NARROW default — the engine is a library and repository semantics are
|
|
277
|
+
* host business; a host wanting the true project root passes an explicit scope. The narrow cost
|
|
278
|
+
* is more asks (a nested-directory rule does not cover siblings), the safe direction;
|
|
279
|
+
* · both absent ⇒ a LOUD refusal (`config.missing_scope`): silent-global was the #490② defect,
|
|
280
|
+
* silent-project has no root to anchor, and "where does this consent land" is not guessable.
|
|
281
|
+
*/
|
|
261
282
|
scope?: RuleScope;
|
|
283
|
+
/**
|
|
284
|
+
* design/382 §4.3 — the SESSION IDENTITY of the adjudicated call, threaded by the caller from the
|
|
285
|
+
* original call context exactly like `cwd` (never inferred from the process). It is the coverage
|
|
286
|
+
* read's third eligibility axis: with it, a standing session rule of that session counts as
|
|
287
|
+
* coverage; without it, no session rule covers anything (fail-closed). Independent of `scope` on
|
|
288
|
+
* purpose — `scope` names where a NEW consent would land, this names where the CALL is running.
|
|
289
|
+
*/
|
|
290
|
+
sessionId?: string;
|
|
262
291
|
/**
|
|
263
292
|
* The working directory of the ADJUDICATED CALL — the same task root the gate's lane judged
|
|
264
293
|
* with — threaded by the caller from the original call context. Never inferred from `scope`
|
|
@@ -400,7 +429,12 @@ export type EditedRuleTextPrecheck = {
|
|
|
400
429
|
* so the surface's move is to not offer the edit box at all.
|
|
401
430
|
*/
|
|
402
431
|
export declare function precheckEditedRuleText(text: string, command: string): EditedRuleTextPrecheck;
|
|
403
|
-
/** What a redemption produced. `alreadyRedeemed` marks the replay path — the same dot, no second rule.
|
|
432
|
+
/** What a redemption produced. `alreadyRedeemed` marks the replay path — the same dot, no second rule.
|
|
433
|
+
*
|
|
434
|
+
* `rev` is the DURABLE store's revision. A session-scope redemption (design/382 §4.3) lands in the
|
|
435
|
+
* session overlay and does not move it: its `rev` is a best-effort read of the store's current
|
|
436
|
+
* revision (0 when the store could not be read — the landing is in the overlay either way, and the
|
|
437
|
+
* durable revision is reporting, not the landing's identity). */
|
|
404
438
|
export type RedeemResult = {
|
|
405
439
|
status: "redeemed";
|
|
406
440
|
rule: string;
|
|
@@ -508,6 +542,12 @@ export interface ImportPreview {
|
|
|
508
542
|
* `false` AND `deduped` — the two axes answer different questions and neither implies the other).
|
|
509
543
|
* A refused row always carries its `reason`. `deduped` counts as LANDED: an equivalent rule
|
|
510
544
|
* already standing means the consent is already in effect.
|
|
545
|
+
*
|
|
546
|
+
* design/382 §4.3 — on a SESSION-scope member the two landed words mean the same thing about a
|
|
547
|
+
* different home: landed in / already in the session's OVERLAY (a session rule's home store), never
|
|
548
|
+
* the persisted store. The words state that the landing happened; DURABILITY is what the member's own
|
|
549
|
+
* `scope` says, so a consumer never has to guess it off the status (deliberately no fourth status
|
|
550
|
+
* value — the closed set names landing outcomes, not storage classes).
|
|
511
551
|
*/
|
|
512
552
|
export type RedeemedBatchMember = {
|
|
513
553
|
readonly candidateIndex: number;
|