pi-daddy 0.43.1 → 0.44.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 -31
- package/README.md +15 -0
- package/THIRD_PARTY_LICENSES/musl-COPYRIGHT.txt +193 -0
- package/THIRD_PARTY_LICENSES/zig-MIT.txt +21 -0
- package/THIRD_PARTY_NOTICES.md +9 -0
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -5
- package/dist/cli.js.map +1 -1
- package/dist/executors/activity-session.js +116 -65
- package/dist/executors/activity-session.js.map +1 -1
- package/dist/executors/captured-execution.d.ts +13 -0
- package/dist/executors/captured-execution.d.ts.map +1 -0
- package/dist/executors/captured-execution.js +53 -0
- package/dist/executors/captured-execution.js.map +1 -0
- package/dist/executors/child-final.d.ts +31 -0
- package/dist/executors/child-final.d.ts.map +1 -0
- package/dist/executors/child-final.js +197 -0
- package/dist/executors/child-final.js.map +1 -0
- package/dist/executors/executor.d.ts +2 -1
- package/dist/executors/executor.d.ts.map +1 -1
- package/dist/executors/executor.js +12 -0
- package/dist/executors/executor.js.map +1 -1
- package/dist/executors/owned-worker.d.ts +18 -0
- package/dist/executors/owned-worker.d.ts.map +1 -0
- package/dist/executors/owned-worker.js +295 -0
- package/dist/executors/owned-worker.js.map +1 -0
- package/dist/executors/worker-artifact.d.ts +3 -0
- package/dist/executors/worker-artifact.d.ts.map +1 -0
- package/dist/executors/worker-artifact.js +3 -0
- package/dist/executors/worker-artifact.js.map +1 -0
- package/dist/governance/approval-prompt.d.ts +4 -3
- package/dist/governance/approval-prompt.d.ts.map +1 -1
- package/dist/governance/approval-prompt.js +13 -6
- package/dist/governance/approval-prompt.js.map +1 -1
- package/dist/governance/captured-worker-record.d.ts +9 -0
- package/dist/governance/captured-worker-record.d.ts.map +1 -0
- package/dist/governance/captured-worker-record.js +123 -0
- package/dist/governance/captured-worker-record.js.map +1 -0
- package/dist/governance/lease-record.d.ts +4 -0
- package/dist/governance/lease-record.d.ts.map +1 -1
- package/dist/governance/lease-record.js +19 -2
- package/dist/governance/lease-record.js.map +1 -1
- package/dist/governance/ledger-events.d.ts +5 -5
- package/dist/governance/ledger-events.d.ts.map +1 -1
- package/dist/governance/ledger-v3-validation.js +1 -1
- package/dist/governance/ledger-v3-validation.js.map +1 -1
- package/dist/governance/workspace-acceptance.d.ts +3 -1
- package/dist/governance/workspace-acceptance.d.ts.map +1 -1
- package/dist/governance/workspace-acceptance.js +21 -5
- package/dist/governance/workspace-acceptance.js.map +1 -1
- package/dist/governance/workspace-lease.d.ts.map +1 -1
- package/dist/governance/workspace-lease.js +44 -1
- package/dist/governance/workspace-lease.js.map +1 -1
- package/dist/kernel/authority-text.d.ts +3 -0
- package/dist/kernel/authority-text.d.ts.map +1 -0
- package/dist/kernel/authority-text.js +22 -0
- package/dist/kernel/authority-text.js.map +1 -0
- package/dist/kernel/bounded-read-failures.d.ts +9 -0
- package/dist/kernel/bounded-read-failures.d.ts.map +1 -0
- package/dist/kernel/bounded-read-failures.js +33 -0
- package/dist/kernel/bounded-read-failures.js.map +1 -0
- package/dist/kernel/bounded-read-sync.d.ts +24 -0
- package/dist/kernel/bounded-read-sync.d.ts.map +1 -0
- package/dist/kernel/bounded-read-sync.js +75 -0
- package/dist/kernel/bounded-read-sync.js.map +1 -0
- package/dist/kernel/bounded-read.d.ts +29 -2
- package/dist/kernel/bounded-read.d.ts.map +1 -1
- package/dist/kernel/bounded-read.js +98 -9
- package/dist/kernel/bounded-read.js.map +1 -1
- package/dist/kernel/capacity.d.ts +36 -0
- package/dist/kernel/capacity.d.ts.map +1 -0
- package/dist/kernel/capacity.js +94 -0
- package/dist/kernel/capacity.js.map +1 -0
- package/dist/kernel/captured-worker-contract.d.ts +38 -0
- package/dist/kernel/captured-worker-contract.d.ts.map +1 -0
- package/dist/kernel/captured-worker-contract.js +2 -0
- package/dist/kernel/captured-worker-contract.js.map +1 -0
- package/dist/kernel/catalog.d.ts +12 -3
- package/dist/kernel/catalog.d.ts.map +1 -1
- package/dist/kernel/catalog.js +28 -8
- package/dist/kernel/catalog.js.map +1 -1
- package/dist/kernel/chain.d.ts +1 -0
- package/dist/kernel/chain.d.ts.map +1 -1
- package/dist/kernel/chain.js.map +1 -1
- package/dist/kernel/definitions.d.ts +11 -1
- package/dist/kernel/definitions.d.ts.map +1 -1
- package/dist/kernel/definitions.js +6 -3
- package/dist/kernel/definitions.js.map +1 -1
- package/dist/kernel/fanout.d.ts +5 -25
- package/dist/kernel/fanout.d.ts.map +1 -1
- package/dist/kernel/fanout.js +15 -30
- package/dist/kernel/fanout.js.map +1 -1
- package/dist/kernel/grant-env.d.ts +0 -2
- package/dist/kernel/grant-env.d.ts.map +1 -1
- package/dist/kernel/grant-env.js +0 -1
- package/dist/kernel/grant-env.js.map +1 -1
- package/dist/kernel/model-preflight.d.ts +8 -0
- package/dist/kernel/model-preflight.d.ts.map +1 -1
- package/dist/kernel/model-preflight.js +23 -0
- package/dist/kernel/model-preflight.js.map +1 -1
- package/dist/kernel/selected-definitions.d.ts +15 -0
- package/dist/kernel/selected-definitions.d.ts.map +1 -0
- package/dist/kernel/selected-definitions.js +146 -0
- package/dist/kernel/selected-definitions.js.map +1 -0
- package/dist/kernel/skill-packages.d.ts +4 -2
- package/dist/kernel/skill-packages.d.ts.map +1 -1
- package/dist/kernel/skill-packages.js +34 -41
- package/dist/kernel/skill-packages.js.map +1 -1
- package/dist/kernel/skill-resources.d.ts.map +1 -1
- package/dist/kernel/skill-resources.js +8 -11
- package/dist/kernel/skill-resources.js.map +1 -1
- package/dist/kernel/workspace.d.ts +6 -3
- package/dist/kernel/workspace.d.ts.map +1 -1
- package/dist/kernel/workspace.js +10 -6
- package/dist/kernel/workspace.js.map +1 -1
- package/dist/products/dashboard-cli.js +1 -1
- package/dist/products/dashboard-cli.js.map +1 -1
- package/dist/products/dashboard-render.d.ts +1 -2
- package/dist/products/dashboard-render.d.ts.map +1 -1
- package/dist/products/dashboard-render.js +3 -1
- package/dist/products/dashboard-render.js.map +1 -1
- package/dist/products/dashboard-session-client.d.ts +1 -2
- package/dist/products/dashboard-session-client.d.ts.map +1 -1
- package/dist/products/dashboard-session-client.js.map +1 -1
- package/dist/products/episode-report.d.ts +12 -8
- package/dist/products/episode-report.d.ts.map +1 -1
- package/dist/products/episode-report.js +61 -12
- package/dist/products/episode-report.js.map +1 -1
- package/extensions/approvals.ts +1 -1
- package/extensions/chain-plan.ts +9 -3
- package/extensions/context-staging.ts +30 -35
- package/extensions/dashboard-session-server.ts +1 -2
- package/extensions/definition-describe.ts +44 -0
- package/extensions/definition-runtime.ts +89 -26
- package/extensions/delegate-chain.ts +273 -256
- package/extensions/delegation-activation.ts +1 -1
- package/extensions/delegation.ts +41 -53
- package/extensions/execute-child.ts +167 -134
- package/extensions/fanout-outcome.ts +1 -9
- package/extensions/grants-command.ts +2 -9
- package/extensions/grants.ts +106 -38
- package/extensions/init-command.ts +52 -12
- package/extensions/reload-environment.ts +21 -2
- package/extensions/retired-inputs.ts +7 -0
- package/extensions/run-delegation.ts +212 -265
- package/extensions/session-capacity.ts +53 -0
- package/extensions/session-environment.ts +5 -2
- package/extensions/session-model-prompt.ts +33 -32
- package/extensions/session-report.ts +28 -11
- package/extensions/session.ts +200 -157
- package/extensions/workspace-runtime.ts +2 -1
- package/native/README.md +25 -0
- package/native/linux-x64/worker +0 -0
- package/native/linux-x64/worker.sha256 +1 -0
- package/native/provenance.json +42 -0
- package/native/toolchain.json +18 -0
- package/native/worker.c +209 -0
- package/package.json +15 -7
- package/src/cli.ts +16 -6
- package/src/executors/activity-session.ts +120 -66
- package/src/executors/captured-execution.ts +61 -0
- package/src/executors/child-final.ts +190 -0
- package/src/executors/executor.ts +14 -1
- package/src/executors/owned-worker.ts +322 -0
- package/src/executors/worker-artifact.ts +2 -0
- package/src/governance/approval-prompt.ts +18 -8
- package/src/governance/captured-worker-record.ts +138 -0
- package/src/governance/lease-record.ts +21 -2
- package/src/governance/ledger-events.ts +5 -5
- package/src/governance/ledger-v3-validation.ts +1 -1
- package/src/governance/workspace-acceptance.ts +20 -4
- package/src/governance/workspace-lease.ts +59 -1
- package/src/kernel/authority-text.ts +17 -0
- package/src/kernel/bounded-read-failures.ts +42 -0
- package/src/kernel/bounded-read-sync.ts +87 -0
- package/src/kernel/bounded-read.ts +128 -11
- package/src/kernel/capacity.ts +123 -0
- package/src/kernel/captured-worker-contract.ts +29 -0
- package/src/kernel/catalog.ts +45 -16
- package/src/kernel/chain.ts +1 -0
- package/src/kernel/definitions.ts +11 -2
- package/src/kernel/fanout.ts +17 -32
- package/src/kernel/grant-env.ts +0 -3
- package/src/kernel/model-preflight.ts +29 -0
- package/src/kernel/selected-definitions.ts +156 -0
- package/src/kernel/skill-packages.ts +40 -38
- package/src/kernel/skill-resources.ts +8 -9
- package/src/kernel/workspace.ts +12 -5
- package/src/products/dashboard-cli.ts +1 -1
- package/src/products/dashboard-render.ts +6 -2
- package/src/products/dashboard-session-client.ts +1 -2
- package/src/products/episode-report.ts +80 -17
- package/dist/advisors/advisor.d.ts +0 -90
- package/dist/advisors/advisor.d.ts.map +0 -1
- package/dist/advisors/advisor.js +0 -154
- package/dist/advisors/advisor.js.map +0 -1
- package/dist/advisors/decider.d.ts +0 -88
- package/dist/advisors/decider.d.ts.map +0 -1
- package/dist/advisors/decider.js +0 -29
- package/dist/advisors/decider.js.map +0 -1
- package/dist/advisors/jev.d.ts +0 -41
- package/dist/advisors/jev.d.ts.map +0 -1
- package/dist/advisors/jev.js +0 -139
- package/dist/advisors/jev.js.map +0 -1
- package/dist/advisors/settings.d.ts +0 -49
- package/dist/advisors/settings.d.ts.map +0 -1
- package/dist/advisors/settings.js +0 -99
- package/dist/advisors/settings.js.map +0 -1
- package/dist/governance/episode-cost-gate.d.ts +0 -31
- package/dist/governance/episode-cost-gate.d.ts.map +0 -1
- package/dist/governance/episode-cost-gate.js +0 -112
- package/dist/governance/episode-cost-gate.js.map +0 -1
- package/extensions/advisor-session.ts +0 -67
- package/extensions/effort-advice.ts +0 -112
- package/extensions/pruning-advice.ts +0 -133
- package/src/advisors/advisor.ts +0 -246
- package/src/advisors/decider.ts +0 -74
- package/src/advisors/jev.ts +0 -163
- package/src/advisors/settings.ts +0 -115
- package/src/governance/episode-cost-gate.ts +0 -128
package/extensions/grants.ts
CHANGED
|
@@ -25,7 +25,8 @@ import { homedir } from "node:os";
|
|
|
25
25
|
import { join, resolve } from "node:path";
|
|
26
26
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
27
|
import { WILDCARD } from "../src/kernel/pi-tools.ts";
|
|
28
|
-
import {
|
|
28
|
+
import { makeCatalog, classifyToolNames } from "../src/kernel/catalog.ts";
|
|
29
|
+
import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
29
30
|
import { ENV_WORKSPACE_REGISTRY } from "../src/kernel/workspace.ts";
|
|
30
31
|
import { appendRecord, buildRecord } from "../src/governance/ledger.ts";
|
|
31
32
|
import { openPaneCount, reapOpenPanesAsync } from "../src/executors/pane-reaper.ts";
|
|
@@ -35,13 +36,20 @@ import { registerDelegationTools } from "./delegation.ts";
|
|
|
35
36
|
import { grantsCommand } from "./grants-command.ts";
|
|
36
37
|
import { runInit } from "./init-command.ts";
|
|
37
38
|
import { planWithApprovals } from "./run-delegation.ts";
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
createGrantsSession,
|
|
41
|
+
loadProjectDefinitions,
|
|
42
|
+
assertDiscoveryHealthy,
|
|
43
|
+
recordDiscoveryCleanupFailure,
|
|
44
|
+
retainDiscoveryCleanupFailure,
|
|
45
|
+
type GrantsSession,
|
|
46
|
+
} from "./session.ts";
|
|
39
47
|
import { acceptWorkspaces } from "../src/governance/workspace-acceptance.ts";
|
|
40
48
|
import { loadWorkspaceRegistry } from "../src/kernel/workspace.ts";
|
|
41
49
|
import { bindReloadLifecycle } from "./reload-environment.ts";
|
|
42
50
|
import { reconcileActiveDelegationTools } from "./delegation-activation.ts";
|
|
43
51
|
import { resolveExecutor } from "./executor-session.ts";
|
|
44
|
-
import { reportSessionStart } from "./session-report.ts";
|
|
52
|
+
import { reportSelectedDefinitions, reportSessionStart, type SessionReportContext } from "./session-report.ts";
|
|
45
53
|
import { SPAWN_TOOLS, tripwireReason } from "./tripwire.ts";
|
|
46
54
|
import { reportGrantStoreRefusal } from "./grant-store-refusal.ts";
|
|
47
55
|
import {
|
|
@@ -58,7 +66,8 @@ import { legacyProjectLedgerPath, projectLedgerPath } from "../src/kernel/projec
|
|
|
58
66
|
import { resolveDefinitionRuntime } from "./definition-runtime.ts";
|
|
59
67
|
import { changeSessionModels, renderSessionModels } from "./session-model-prompt.ts";
|
|
60
68
|
import { ensureDashboardSessionServer } from "./dashboard-session-server.ts";
|
|
61
|
-
|
|
69
|
+
/** Optional trusted session injection is composition-only, never provider payload input. */
|
|
70
|
+
export default function (pi: ExtensionAPI, trustedSession?: GrantsSession) {
|
|
62
71
|
// The path pi loads as the extension, so a child granted `tool:delegate` can be started with `-e <this>`.
|
|
63
72
|
const extensionPath = (() => {
|
|
64
73
|
try {
|
|
@@ -68,13 +77,34 @@ export default function (pi: ExtensionAPI) {
|
|
|
68
77
|
}
|
|
69
78
|
})();
|
|
70
79
|
const observerExtensionPath = fileURLToPath(new URL("./activity-timeline.ts", import.meta.url));
|
|
71
|
-
const session = createGrantsSession(extensionPath, undefined, observerExtensionPath);
|
|
80
|
+
const session = trustedSession ?? createGrantsSession(extensionPath, undefined, observerExtensionPath);
|
|
81
|
+
session.currentThinking = typeof pi.getThinkingLevel === "function" ? () => pi.getThinkingLevel() : undefined;
|
|
72
82
|
registerActivityTimeline(pi, session);
|
|
73
83
|
const dashboardPluginRoot = fileURLToPath(new URL("../herdr-plugin/", import.meta.url));
|
|
74
84
|
const dashboardPaths = defaultDashboardPaths(agentDir());
|
|
75
85
|
// Definitions are registered only after owner-bound session_start. Until then there is no delegation
|
|
76
86
|
// dispatch surface; afterwards this callback refreshes their model-facing spawnable-definition text.
|
|
77
87
|
const delegation = { refreshSpawnable: () => {} };
|
|
88
|
+
// Supported public discovery is ready at the first real request/tool execution, not session_start.
|
|
89
|
+
let selectedReady: Promise<void> | undefined;
|
|
90
|
+
let selectedReportContext: SessionReportContext | undefined;
|
|
91
|
+
if (typeof pi.getCommands === "function")
|
|
92
|
+
session.ensureDefinitions = () =>
|
|
93
|
+
(selectedReady ??= (async () => {
|
|
94
|
+
const lifecycle = session.reloadLifecycle;
|
|
95
|
+
await loadProjectDefinitions(session, session.cwd, lifecycle, pi.getCommands());
|
|
96
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
97
|
+
delegation.refreshSpawnable();
|
|
98
|
+
if (selectedReportContext)
|
|
99
|
+
try {
|
|
100
|
+
await reportSelectedDefinitions(session, selectedReportContext);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error("grants: selected resource report unavailable", error);
|
|
103
|
+
}
|
|
104
|
+
})());
|
|
105
|
+
pi.on("before_agent_start", async () => {
|
|
106
|
+
await session.ensureDefinitions?.();
|
|
107
|
+
});
|
|
78
108
|
pi.on("session_start", async (_event, ctx) => {
|
|
79
109
|
if (session.adoptedLegacyEnv.length > 0)
|
|
80
110
|
ctx.ui.notify(legacyEnvironmentWarning(session.adoptedLegacyEnv), "warning");
|
|
@@ -84,6 +114,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
84
114
|
const reload = bindReloadLifecycle(owner, session.reloadLifecycle);
|
|
85
115
|
session.reconcileEnvironment(reload.environment, reload.lifecycle);
|
|
86
116
|
session.ownerBound = true;
|
|
117
|
+
selectedReady = undefined;
|
|
118
|
+
selectedReportContext = ctx;
|
|
87
119
|
// ADR-0078: the parent's own session, for a granted `pruned` or `fork` handoff. Read-only, and only ever
|
|
88
120
|
// read after the mode has survived the gate.
|
|
89
121
|
const manager = owner as Partial<import("./context-staging.ts").ParentSession>;
|
|
@@ -96,7 +128,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
96
128
|
// execution tree across workspaces. Resolve it once at the root's actual pi cwd; descendants inherit
|
|
97
129
|
// this absolute identity verbatim, and resolve(abs) remains abs at every deeper session start.
|
|
98
130
|
if (session.ledgerPath) session.ledgerPath = resolve(ctx.cwd, session.ledgerPath);
|
|
99
|
-
reconcileAdvisorSession(session, reload.environment);
|
|
100
131
|
try {
|
|
101
132
|
// ADR-0076 PR 3d: a pre-format project ledger is imported once into the record envelope, bodies verbatim, each
|
|
102
133
|
// record marked with its source line; the old file is left untouched. Happens before any spawn can append.
|
|
@@ -105,6 +136,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
105
136
|
// continues against the new ledger path (an append there still fails closed if that path is unwritable).
|
|
106
137
|
try {
|
|
107
138
|
const imported = await importLegacyLedger(legacyProjectLedgerPath(ctx.cwd), projectLedgerPath(ctx.cwd));
|
|
139
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
108
140
|
if (imported.imported > 0 || imported.stoppedAt !== null)
|
|
109
141
|
ctx.ui.notify(
|
|
110
142
|
`grants: imported ${imported.imported} record(s) from ${legacyProjectLedgerPath(ctx.cwd)} into ${projectLedgerPath(ctx.cwd)}` +
|
|
@@ -115,6 +147,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
115
147
|
imported.stoppedAt !== null ? "warning" : "info",
|
|
116
148
|
);
|
|
117
149
|
} catch (error) {
|
|
150
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
118
151
|
ctx.ui.notify(`grants: legacy ledger import failed: ${String(error)}`, "error");
|
|
119
152
|
}
|
|
120
153
|
}
|
|
@@ -139,11 +172,13 @@ export default function (pi: ExtensionAPI) {
|
|
|
139
172
|
try {
|
|
140
173
|
await reportGrantStoreRefusal(session, ctx.ui);
|
|
141
174
|
} catch (error) {
|
|
175
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
142
176
|
ctx.ui.notify(
|
|
143
177
|
`grants: stored-grant refusal reporting failed (${String(error)}); the session remains refused.`,
|
|
144
178
|
"error",
|
|
145
179
|
);
|
|
146
180
|
}
|
|
181
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
147
182
|
// Guarded together, and guarded at all because of R-60 rather than because either one throws today:
|
|
148
183
|
// both loaders swallow their own filesystem errors, so this catch is currently unreachable. The point
|
|
149
184
|
// is that "currently" is not a property anyone can rely on — `verifyLedger` was also harmless until
|
|
@@ -152,16 +187,28 @@ export default function (pi: ExtensionAPI) {
|
|
|
152
187
|
// delegate by `tools:`, and an operator whose `agent:` spawns have all started failing deserves to
|
|
153
188
|
// know it was the *scan* that broke rather than the grant.
|
|
154
189
|
try {
|
|
155
|
-
await loadProjectDefinitions(session, ctx.cwd);
|
|
190
|
+
await loadProjectDefinitions(session, ctx.cwd, reload.lifecycle, session.ensureDefinitions ? [] : undefined);
|
|
156
191
|
} catch (error) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
192
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
193
|
+
if (error instanceof BoundedReadCleanupError) {
|
|
194
|
+
try {
|
|
195
|
+
ctx.ui.notify(
|
|
196
|
+
`grants: discovery cleanup failed — ${error.message}; delegation remains failed until a new owner.`,
|
|
197
|
+
"error",
|
|
198
|
+
);
|
|
199
|
+
} catch (diagnosticError) {
|
|
200
|
+
console.error("grants: startup cleanup diagnostic failed", error, diagnosticError);
|
|
201
|
+
}
|
|
202
|
+
} else
|
|
203
|
+
ctx.ui.notify(
|
|
204
|
+
`grants: could not read this project's definitions or capability catalog ` +
|
|
205
|
+
`(${error instanceof Error ? error.message : String(error)}) — no SKILL.md definition can be ` +
|
|
206
|
+
`spawned this session, and delegation by tools: is unaffected. Governance itself is unaffected: ` +
|
|
207
|
+
`it is enforced by --tools when a child is spawned.`,
|
|
208
|
+
"error",
|
|
209
|
+
);
|
|
164
210
|
}
|
|
211
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
165
212
|
// ADR-0031: probe once, HERE, before anything reports — so the disclosure line can name the executor,
|
|
166
213
|
// and so a demanded-but-unreachable herdr is reported before the operator's first prompt rather than at
|
|
167
214
|
// their first delegation. Its own try, because a failure here must not cancel the controls after it
|
|
@@ -169,6 +216,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
216
|
try {
|
|
170
217
|
await resolveExecutor(session);
|
|
171
218
|
} catch (error) {
|
|
219
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
172
220
|
// Says what is actually true of the state left behind, which depends on the variable: with
|
|
173
221
|
// `PI_DADDY_HERDR=1` the session holds a REFUSAL and every delegation fails, so telling the operator
|
|
174
222
|
// "using the captured subprocess" would be the opposite of what happens. The old wording asserted the
|
|
@@ -184,10 +232,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
184
232
|
"warning",
|
|
185
233
|
);
|
|
186
234
|
}
|
|
235
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
187
236
|
session.publishChildEnv();
|
|
188
237
|
// The definitions now exist, so the `delegate` schema can finally name them (R-39). pi serialises a
|
|
189
238
|
// tool's schema at REQUEST time, not at registration — measured — which is what makes this reach the
|
|
190
239
|
// model at all.
|
|
240
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
191
241
|
delegation.refreshSpawnable();
|
|
192
242
|
// Everything an operator is TOLD at session start now lives in `./session-report.ts`. Lifted because
|
|
193
243
|
// this file had reached 398 of the 400-line ceiling and ADR-0032 adds a control to it; the split is the
|
|
@@ -203,6 +253,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
203
253
|
try {
|
|
204
254
|
await reportSessionStart(session, ctx);
|
|
205
255
|
} catch (error) {
|
|
256
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
206
257
|
ctx.ui.notify(
|
|
207
258
|
// Names the CHECKS as well as the display lines. The first version listed only "the grant, the
|
|
208
259
|
// executor and the spawnable definitions" — which understated it: a throw partway through the reporter
|
|
@@ -218,9 +269,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
218
269
|
"warning",
|
|
219
270
|
);
|
|
220
271
|
}
|
|
272
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
221
273
|
// Explicit installation handshake. It only runs in a Herdr-hosted TUI, asks once, and the only branch
|
|
222
274
|
// that links software is the literal human choice "Install and open".
|
|
223
275
|
try {
|
|
276
|
+
const sessionEndpoint = await ensureDashboardSessionServer(session);
|
|
277
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
224
278
|
await offerDashboardHandshake({
|
|
225
279
|
mode: ctx.mode,
|
|
226
280
|
env: process.env,
|
|
@@ -230,10 +284,12 @@ export default function (pi: ExtensionAPI) {
|
|
|
230
284
|
pluginRoot: dashboardPluginRoot,
|
|
231
285
|
preferencePath: dashboardPaths.preferencePath,
|
|
232
286
|
paneStatePath: dashboardPaths.paneStatePath,
|
|
233
|
-
sessionEndpoint
|
|
287
|
+
sessionEndpoint,
|
|
234
288
|
ui: ctx.ui,
|
|
235
289
|
});
|
|
290
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
236
291
|
} catch (error) {
|
|
292
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
237
293
|
ctx.ui.notify(
|
|
238
294
|
`grants: dashboard installation handshake could not run (${error instanceof Error ? error.message : String(error)}). ` +
|
|
239
295
|
`Nothing was installed; /grants dashboard will report manual setup.`,
|
|
@@ -241,6 +297,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
241
297
|
);
|
|
242
298
|
}
|
|
243
299
|
} catch (error) {
|
|
300
|
+
if (session.reloadLifecycle !== reload.lifecycle) return undefined;
|
|
244
301
|
// Rule 8 — fail closed, and be LOUD about it. Swallowing is still right: a startup fault must not
|
|
245
302
|
// reach the agent loop. Swallowing SILENTLY is what let R-60 exist, and would let the next one exist
|
|
246
303
|
// too, because every control added above this line is cancelled by any throw before it with no trace.
|
|
@@ -252,8 +309,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
252
309
|
`may refuse. The grant itself is unaffected: it is enforced by --tools when a child is spawned.`,
|
|
253
310
|
"error",
|
|
254
311
|
);
|
|
255
|
-
} catch {
|
|
256
|
-
|
|
312
|
+
} catch (diagnosticError) {
|
|
313
|
+
console.error("grants: session-start diagnostic failed", error, diagnosticError);
|
|
257
314
|
}
|
|
258
315
|
}
|
|
259
316
|
return undefined;
|
|
@@ -288,7 +345,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
288
345
|
|
|
289
346
|
// Observe this session's real tool surface once, and tighten the grant to it. Authoritative because
|
|
290
347
|
// it is exactly what pi sent the model.
|
|
291
|
-
pi.on("before_provider_request", (event) => {
|
|
348
|
+
pi.on("before_provider_request", (event, ctx) => {
|
|
292
349
|
try {
|
|
293
350
|
if (session.observed) return undefined;
|
|
294
351
|
const names = observeToolNames(event.payload);
|
|
@@ -303,15 +360,17 @@ export default function (pi: ExtensionAPI) {
|
|
|
303
360
|
// Refresh the catalog now that the real tool surface is known — this is the only moment extension
|
|
304
361
|
// tools become visible, so it is the only moment `ext:`/`tool:` grants can be validated.
|
|
305
362
|
// Keep the handle: a concurrent `delegate` awaits this rather than reading a half-built catalog.
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
session.catalogReady =
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
363
|
+
const lifecycle = session.reloadLifecycle;
|
|
364
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
365
|
+
session.catalogReady = session.catalogReady.then((catalog) => {
|
|
366
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
367
|
+
const next = makeCatalog([...catalog.entries, ...classifyToolNames(names)], catalog.registryRefusal);
|
|
368
|
+
session.catalog = next;
|
|
369
|
+
return next;
|
|
370
|
+
});
|
|
371
|
+
void session.catalogReady.catch((error: unknown) => {
|
|
372
|
+
if (error instanceof BoundedReadCleanupError) recordDiscoveryCleanupFailure(session, error, lifecycle);
|
|
373
|
+
});
|
|
315
374
|
} catch {
|
|
316
375
|
/* never throw into the agent loop */
|
|
317
376
|
}
|
|
@@ -370,8 +429,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
370
429
|
...grantsCommand,
|
|
371
430
|
// Built per invocation and spelled out field by field, rather than passing the session whole: what a
|
|
372
431
|
// read-only diagnostic may see is a decision, and `GrantsCommandContext` is where it is recorded.
|
|
373
|
-
handler: (args, ctx) =>
|
|
374
|
-
|
|
432
|
+
handler: async (args, ctx) => {
|
|
433
|
+
// Named revocation validates the current definition. Emergency revoke-all needs no resource acquisition.
|
|
434
|
+
if (args.trim() !== "revoke --all") await session.ensureDefinitions?.();
|
|
435
|
+
return grantsCommand.handler(args, {
|
|
375
436
|
...ctx,
|
|
376
437
|
grants: {
|
|
377
438
|
cwd: session.cwd,
|
|
@@ -382,11 +443,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
382
443
|
depth: session.depth,
|
|
383
444
|
maxDepth: session.maxDepth,
|
|
384
445
|
ledgerPath: session.ledgerPath,
|
|
385
|
-
advisor: {
|
|
386
|
-
decider: session.advisorSession.deciderName,
|
|
387
|
-
taskEgress: session.advisorSession.settings.taskEgress,
|
|
388
|
-
...(session.advisorSession.settings.refusal ? { refusal: session.advisorSession.settings.refusal } : {}),
|
|
389
|
-
},
|
|
390
446
|
...(session.workspacePin ? { workspacePin: session.workspacePin } : {}),
|
|
391
447
|
...(session.workspaceAcceptance ? { unacceptedWorkspaces: session.workspaceAcceptance.unaccepted } : {}),
|
|
392
448
|
// Injected rather than imported, so `/grants` takes its one writing ability visibly from here.
|
|
@@ -413,8 +469,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
413
469
|
// (R-38). Passing `ctx` here would let `/grants` raise a dialog, and passing `hasUI: false` would
|
|
414
470
|
// make every gated definition report "no interactive user" instead of what actually blocks it.
|
|
415
471
|
runInit: () =>
|
|
416
|
-
runInit(session, ctx, async () => {
|
|
417
|
-
|
|
472
|
+
runInit(session, ctx, async (lifecycle) => {
|
|
473
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
474
|
+
if (session.ensureDefinitions) await session.ensureDefinitions();
|
|
475
|
+
else await loadProjectDefinitions(session, ctx.cwd, lifecycle);
|
|
476
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
418
477
|
delegation.refreshSpawnable();
|
|
419
478
|
}),
|
|
420
479
|
openDashboard: async () =>
|
|
@@ -434,7 +493,15 @@ export default function (pi: ExtensionAPI) {
|
|
|
434
493
|
input(title: string, placeholder?: string): Promise<string | undefined>;
|
|
435
494
|
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
436
495
|
}) => {
|
|
437
|
-
|
|
496
|
+
const lifecycle = session.reloadLifecycle;
|
|
497
|
+
await changeSessionModels(
|
|
498
|
+
session,
|
|
499
|
+
{ hasUI: ctx.hasUI, ...ui },
|
|
500
|
+
{
|
|
501
|
+
assertHealthy: () => assertDiscoveryHealthy(session, lifecycle),
|
|
502
|
+
onReadCleanup: (error) => retainDiscoveryCleanupFailure(session, error, lifecycle),
|
|
503
|
+
},
|
|
504
|
+
);
|
|
438
505
|
return renderSessionModels(session);
|
|
439
506
|
},
|
|
440
507
|
runtimeFor: (name: string) =>
|
|
@@ -446,6 +513,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
446
513
|
piModel: ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined,
|
|
447
514
|
}),
|
|
448
515
|
},
|
|
449
|
-
})
|
|
516
|
+
});
|
|
517
|
+
},
|
|
450
518
|
});
|
|
451
519
|
}
|
|
@@ -13,7 +13,9 @@ import { PI_PROJECT_DIR } from "../src/kernel/project-paths.ts";
|
|
|
13
13
|
import { registeredWorkspaceIds } from "../src/kernel/workspace.ts";
|
|
14
14
|
import { grantStorePath, projectLedgerPath, saveGrant } from "../src/governance/grant-store.ts";
|
|
15
15
|
import { expandSubsumed, SUBSUMPTION, type Capability } from "../src/kernel/resolve.ts";
|
|
16
|
-
import type
|
|
16
|
+
import { assertDiscoveryHealthy, retainDiscoveryCleanupFailure, type GrantsSession } from "./session.ts";
|
|
17
|
+
import type { ReloadLifecycle } from "./reload-environment.ts";
|
|
18
|
+
import { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* `/grants init` — scaffold, ask about what is withheld, store grant + ledger consent, apply both now.
|
|
@@ -34,15 +36,40 @@ export async function runInit(
|
|
|
34
36
|
/**
|
|
35
37
|
* Reload definitions and the catalog, and re-describe the delegation tools.
|
|
36
38
|
*
|
|
39
|
+
* Refresh selected definitions for the live grant. New Pi resource references still need explicit /reload.
|
|
37
40
|
* **Without this the grant goes live and the definitions do not** — `session.definitions` and the catalog
|
|
38
41
|
* are read at `session_start`, which is before `init` wrote a single file, so a session would hold
|
|
39
42
|
* `agent:review` while believing no definition of that name exists. `/grants` showed `0 skill,
|
|
40
43
|
* 0 agent-type` and no verdicts, and the model would have been told `Available: none` — R-39 exactly,
|
|
41
44
|
* reintroduced by a feature whose whole selling point is "no restart". Found by running it.
|
|
42
45
|
*/
|
|
43
|
-
refresh: () => Promise<void>,
|
|
46
|
+
refresh: (lifecycle: ReloadLifecycle) => Promise<void>,
|
|
44
47
|
): Promise<void> {
|
|
45
|
-
const
|
|
48
|
+
const lifecycle = session.reloadLifecycle;
|
|
49
|
+
try {
|
|
50
|
+
await runOwnedInit(session, ctx, refresh, lifecycle);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error instanceof BoundedReadCleanupError) {
|
|
53
|
+
retainDiscoveryCleanupFailure(session, error, lifecycle);
|
|
54
|
+
try {
|
|
55
|
+
ctx.ui.notify(`grants: init cleanup failed — ${error.message}; init did not complete.`, "error");
|
|
56
|
+
} catch (diagnosticError) {
|
|
57
|
+
console.error("grants: init cleanup diagnostic failed", error, diagnosticError);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function runOwnedInit(
|
|
65
|
+
session: GrantsSession,
|
|
66
|
+
ctx: any,
|
|
67
|
+
refresh: (lifecycle: ReloadLifecycle) => Promise<void>,
|
|
68
|
+
lifecycle: ReloadLifecycle,
|
|
69
|
+
): Promise<void> {
|
|
70
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
71
|
+
const packages = await (session.discovery?.packages ?? discoverSkillPackages)(ctx.cwd);
|
|
72
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
46
73
|
if (packages.length === 0) {
|
|
47
74
|
ctx.ui.notify(
|
|
48
75
|
"grants: no enabled configured skills or unregistered npm skill packages found. Install and enable one — e.g. " +
|
|
@@ -52,14 +79,18 @@ export async function runInit(
|
|
|
52
79
|
return;
|
|
53
80
|
}
|
|
54
81
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
ctx.ui.notify(`grants: workspace registry unreadable, scaffolding no workspaces — ${reason}`, "warning")
|
|
60
|
-
|
|
82
|
+
const workspaces = await registeredWorkspaceIds(
|
|
83
|
+
undefined,
|
|
84
|
+
(reason) => {
|
|
85
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
86
|
+
ctx.ui.notify(`grants: workspace registry unreadable, scaffolding no workspaces — ${reason}`, "warning");
|
|
87
|
+
},
|
|
88
|
+
session.discovery?.registry,
|
|
61
89
|
);
|
|
90
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
91
|
+
const plan = planInit(packages, ctx.cwd, workspaces);
|
|
62
92
|
const outcome = await applyInit(plan);
|
|
93
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
63
94
|
const lines = [
|
|
64
95
|
`grants: ${plan.skills.length} definition(s) from ${packages.map((p) => `${p.name}@${p.version}`).join(", ")}`,
|
|
65
96
|
` using ${plan.skills.filter((s) => s.referenced).length} enabled definition(s) in place; no copies`,
|
|
@@ -77,6 +108,7 @@ export async function runInit(
|
|
|
77
108
|
const alreadyConferred: string[] = [];
|
|
78
109
|
|
|
79
110
|
for (const [capability, neededBy] of plan.withheldCapabilities) {
|
|
111
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
80
112
|
// **Do not ask a question whose answer cannot matter.** `tool:bash` subsumes `write`, `edit` and
|
|
81
113
|
// `edit-diff` (`SUBSUMPTION`, `src/kernel/resolve.ts`), so once bash is granted those are already conferred.
|
|
82
114
|
// The first version asked anyway: an operator could answer *no* to `tool:write`, watch `/grants` allow
|
|
@@ -114,6 +146,7 @@ export async function runInit(
|
|
|
114
146
|
: "it is not gated, so no dialog at spawn time"),
|
|
115
147
|
["No", "Yes"],
|
|
116
148
|
);
|
|
149
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
117
150
|
if (answer === "Yes") {
|
|
118
151
|
grant.add(capability);
|
|
119
152
|
granted.push(capability);
|
|
@@ -125,9 +158,11 @@ export async function runInit(
|
|
|
125
158
|
}
|
|
126
159
|
}
|
|
127
160
|
|
|
161
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
128
162
|
const finalGrant = [...grant].sort();
|
|
129
163
|
const ledger = projectLedgerPath(ctx.cwd);
|
|
130
164
|
const saved = await saveGrant(ctx.cwd, finalGrant, { projectLedger: true });
|
|
165
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
131
166
|
if (saved !== "saved") {
|
|
132
167
|
lines.push(
|
|
133
168
|
` NOT STORED — ${saved === "busy" ? "another session holds the grant store" : "the store could not be written"}. ` +
|
|
@@ -142,7 +177,8 @@ export async function runInit(
|
|
|
142
177
|
session.adoptGrant(finalGrant, ledger);
|
|
143
178
|
// Order matters: the grant first, then the reload, because `refreshSpawnable` filters the definitions it
|
|
144
179
|
// advertises through `maySpawnDefinition` against the grant the session now holds.
|
|
145
|
-
await refresh();
|
|
180
|
+
await refresh(lifecycle);
|
|
181
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
146
182
|
|
|
147
183
|
lines.push(
|
|
148
184
|
` stored at ${grantStorePath(ctx.cwd)} — outside this project, so no child can rewrite it`,
|
|
@@ -164,12 +200,16 @@ export async function runInit(
|
|
|
164
200
|
`${plan.settingsPath}. Which worktree a child starts in is not something a package can declare for you.`,
|
|
165
201
|
);
|
|
166
202
|
}
|
|
167
|
-
|
|
203
|
+
const settingsIgnored = await settingsIgnoredByGit(plan.settingsPath);
|
|
204
|
+
assertDiscoveryHealthy(session, lifecycle);
|
|
205
|
+
if (settingsIgnored === true)
|
|
168
206
|
lines.push(
|
|
169
207
|
` NOTE: git ignores ${plan.settingsPath} (your root .gitignore covers ${PI_PROJECT_DIR}/), so the reviewable record ` +
|
|
170
208
|
`cannot be committed until you add: ${GITIGNORE_REINCLUDE_LINES.join(" ")}`,
|
|
171
209
|
);
|
|
172
|
-
lines.push(` live now (${finalGrant.length} capabilities)
|
|
210
|
+
lines.push(` grant live now (${finalGrant.length} capabilities). /grants shows current selected definitions.`);
|
|
211
|
+
if (session.ensureDefinitions)
|
|
212
|
+
lines.push(" Newly added skill resources require /reload before selection; the current snapshot stays pinned.");
|
|
173
213
|
ctx.ui.notify(lines.join("\n"), "info");
|
|
174
214
|
}
|
|
175
215
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import type { OwnerCapacity } from "./session-capacity.ts";
|
|
1
2
|
import { GRANT_ENV_KEYS } from "../src/kernel/propagation.ts";
|
|
3
|
+
import type { BoundedReadCleanupError } from "../src/kernel/bounded-read.ts";
|
|
2
4
|
|
|
3
5
|
/** The root baseline and latest child publication for one real Pi session owner. */
|
|
4
6
|
export interface ReloadLifecycle {
|
|
5
7
|
root: Record<string, string | undefined>;
|
|
8
|
+
/** Never replaced for the same owner, even when an explicit root environment changes. */
|
|
9
|
+
capacity?: OwnerCapacity;
|
|
6
10
|
published?: Record<string, string | undefined>;
|
|
7
11
|
activityRootId?: string;
|
|
8
12
|
episodeId?: string;
|
|
@@ -20,6 +24,12 @@ export interface ReloadLifecycle {
|
|
|
20
24
|
* than its child publication", so it is where settled-ness belongs.
|
|
21
25
|
*/
|
|
22
26
|
workspacePin?: ReadonlyMap<string, string>;
|
|
27
|
+
/** Failed pin acquisition stays failed for this owner even after explicit physical cleanup or reload. */
|
|
28
|
+
workspacePinFailure?: BoundedReadCleanupError;
|
|
29
|
+
/** One charged acquisition per actual owner, published before trusted loader callbacks. */
|
|
30
|
+
workspacePinAcquisition?: Promise<ReadonlyMap<string, string>>;
|
|
31
|
+
/** Catalog failure is terminal for this lifecycle too, not merely one extension object. */
|
|
32
|
+
discoveryCleanupFailure?: BoundedReadCleanupError;
|
|
23
33
|
}
|
|
24
34
|
type SessionOwner = object;
|
|
25
35
|
|
|
@@ -62,7 +72,7 @@ export function bindReloadLifecycle(
|
|
|
62
72
|
environment: NodeJS.ProcessEnv;
|
|
63
73
|
} {
|
|
64
74
|
const holder = state();
|
|
65
|
-
|
|
75
|
+
let existing = holder.owners.get(owner);
|
|
66
76
|
if (!existing) {
|
|
67
77
|
holder.owners.set(owner, provisional);
|
|
68
78
|
return { lifecycle: provisional, environment: withRoot(provisional.root) };
|
|
@@ -82,7 +92,16 @@ export function bindReloadLifecycle(
|
|
|
82
92
|
// silently ignored for the pin, so a session reconciled to depth 1 — believing itself a descendant —
|
|
83
93
|
// kept a root-minted pin naming a workspace the replacement root never granted. Deleting it makes the
|
|
84
94
|
// next `establishRootPin` re-settle from the new root, which for a descendant means minting nothing.
|
|
85
|
-
if (!same(current, existing.root))
|
|
95
|
+
if (!same(current, existing.root)) {
|
|
96
|
+
// Pending old-root operations keep THEIR original owner; they cannot publish/fault this replacement.
|
|
97
|
+
existing = {
|
|
98
|
+
root: current,
|
|
99
|
+
activityRootId: existing.activityRootId,
|
|
100
|
+
episodeId: existing.episodeId,
|
|
101
|
+
capacity: existing.capacity,
|
|
102
|
+
};
|
|
103
|
+
holder.owners.set(owner, existing);
|
|
104
|
+
}
|
|
86
105
|
existing.root = current;
|
|
87
106
|
}
|
|
88
107
|
return { lifecycle: existing, environment: withRoot(existing.root) };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** Closed compatibility list. Preparation precedes Pi schema validation; active arguments still validate. */
|
|
2
|
+
export function withoutRetiredDelegationArguments<T>(args: unknown): T {
|
|
3
|
+
if (args === null || typeof args !== "object" || Array.isArray(args)) return args as T;
|
|
4
|
+
const clean = { ...(args as Record<string, unknown>) };
|
|
5
|
+
delete clean.episodeCostCeiling;
|
|
6
|
+
return clean as T;
|
|
7
|
+
}
|