pi-delegation-policy 0.23.0 → 0.23.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +47 -0
- package/README.md +11 -9
- package/SECURITY.md +5 -4
- package/package.json +1 -1
- package/src/advisor-executor.ts +0 -8
- package/src/agent-launch.ts +18 -8
- package/src/context-shunt-adapter.ts +2 -1
- package/src/context-shunt-executor.ts +0 -2
- package/src/context-shunt.ts +30 -12
- package/src/index.ts +39 -8
- package/src/prompt.ts +13 -7
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.23.2 - 2026-10-31
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `consult_advisor` now tells the model what to do for the effective Advisor state instead of
|
|
10
|
+
suggesting a switch to tool mode. Off means no consultation; role mode redirects to one background
|
|
11
|
+
host subagent call with every valid Advisor's exact profile, model and thinking policy, and says
|
|
12
|
+
not to call this tool again. Unavailable entries report their existing diagnostics. Invalid saved
|
|
13
|
+
Advisor settings explicitly require informing the user to save them again in `/delegate`, without
|
|
14
|
+
retrying the tool; ordinary availability errors do not ask for configuration changes.
|
|
15
|
+
- Busy, blank-question and missing-session replies now state the next action. ContextShunt recovery
|
|
16
|
+
errors also explain whether to use another range, obtain live evidence or continue without recovery.
|
|
17
|
+
The reader's JSON error contract is unchanged.
|
|
18
|
+
|
|
19
|
+
### Compatibility and verification
|
|
20
|
+
|
|
21
|
+
- No configuration, schema, dependency, profile, permission or Pi minimum change. Upgrade and reload;
|
|
22
|
+
no migration is needed. Tool definitions stay fixed across modes and requests. The tool description
|
|
23
|
+
changes once on upgrade, but no additional policy-block or tool-definition variation is introduced.
|
|
24
|
+
- Registered-tool tests cover off, role, invalid and valid tool calls, exact thinking rules and stable
|
|
25
|
+
definitions. Saved-state guidance is tested at the message constructor without corrupt files; its
|
|
26
|
+
connection to the registered tool was reviewed. Policy output was independently checked byte for
|
|
27
|
+
byte against the previous release. These checks do not measure provider cache hits or establish
|
|
28
|
+
real-model behavior after an accidental role-mode tool call.
|
|
29
|
+
|
|
30
|
+
## 0.23.1 - 2026-10-29
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Advisor and ContextShunt reader launches no longer reject later `pi-subagents` releases solely
|
|
35
|
+
because their package version is not on an exact list. Both require a valid stable SemVer version
|
|
36
|
+
`>=0.76.1`, with no upper bound. Prereleases, invalid versions and non-string values are rejected;
|
|
37
|
+
build metadata does not affect precedence.
|
|
38
|
+
- All other launch checks remain unchanged: contract and lifecycle version 3, definition projection
|
|
39
|
+
version 2, packaged profile and shadowing, exact model and thinking, tool and extension lists,
|
|
40
|
+
permission runtime, session identity and terminal digest binding. An incompatible preflight
|
|
41
|
+
still fails closed before a request is sent; a terminal mismatch still discards the reply.
|
|
42
|
+
|
|
43
|
+
### Compatibility and verification
|
|
44
|
+
|
|
45
|
+
- Pi `0.87.1` remains the package minimum. No dependency, configuration, schema, provider option or
|
|
46
|
+
permission change. Upgrade and reload; no configuration migration is needed.
|
|
47
|
+
- The final loader was exercised against real `pi-subagents` `0.84.0` exports with a synthetic event
|
|
48
|
+
host on Pi `1.4.0`: Advisor fresh/fork and reader fresh completed with terminal binding. This
|
|
49
|
+
checks admission, not real child execution, provider transport or personal permission rules.
|
|
50
|
+
Version labels are not signatures or a guarantee of future compatibility.
|
|
51
|
+
|
|
5
52
|
## 0.23.0 - 2026-10-29
|
|
6
53
|
|
|
7
54
|
### Added
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ general subagent runner. Only explicit tools can request reader work or Advisor
|
|
|
6
6
|
the authorized host executor; hooks never launch children.
|
|
7
7
|
|
|
8
8
|
> **Docs:** [yivas.github.io/pi-delegation-policy](https://yivas.github.io/pi-delegation-policy/).
|
|
9
|
-
> Version **0.23.
|
|
9
|
+
> Version **0.23.2** requires Pi `0.87.1` or later (`>=0.87.1`); it re-applies its marked policy block in the final provider payload.
|
|
10
10
|
> Version `0.14.1` supports Pi `0.84.3` or later (`>=0.84.3`).
|
|
11
11
|
>
|
|
12
12
|
> Independent Advisor mode and its companion file are available from version `0.17.0`.
|
|
@@ -15,7 +15,7 @@ the authorized host executor; hooks never launch children.
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pi install npm:pi-delegation-policy@0.23.
|
|
18
|
+
pi install npm:pi-delegation-policy@0.23.2
|
|
19
19
|
# restart Pi, or run /reload
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -77,10 +77,10 @@ same question. Weigh disagreements; agreement is not proof. The packaged no-tool
|
|
|
77
77
|
- `role`: the main agent launches every valid advisor in one background subagent call, with exact
|
|
78
78
|
models and thinking. The extension observes matching attempts but does not launch them.
|
|
79
79
|
- `tool`: the always-registered `consult_advisor({ question, brief? })` launches one child per valid
|
|
80
|
-
advisor in parallel through the public `pi-subagents` delegation API
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
fallback is added.
|
|
80
|
+
advisor in parallel through the public `pi-subagents` delegation API. It requires a stable SemVer
|
|
81
|
+
version `>=0.76.1`, with no upper bound; prereleases and invalid versions are rejected. Every
|
|
82
|
+
launch must still pass the strict contract and terminal-binding checks. Otherwise it returns
|
|
83
|
+
unavailable text. No model, provider or executor fallback is added.
|
|
84
84
|
|
|
85
85
|
Both modes ask for consultation after orientation before substantive work, when stuck, before changing
|
|
86
86
|
approach, and before declaring a multi-step task done (ask for remaining risks, not approval). Short
|
|
@@ -264,9 +264,11 @@ one bounded line or byte range. Errors, structured results, images, binaries, mi
|
|
|
264
264
|
unknown tool contracts stay unchanged, and nothing is launched from a hook. The reader keys
|
|
265
265
|
`readerEnabled`, `readerRole`, and `answerMaxBytes` control the optional
|
|
266
266
|
`context_shunt_delegate`/`context_shunt_recover` pair, which needs `enforce`, an active delegation
|
|
267
|
-
intensity, a valid configuration, and a compatible external executor. The
|
|
268
|
-
`pi-subagents`
|
|
269
|
-
|
|
267
|
+
intensity, a valid configuration, and a compatible external executor. The reader requires stable
|
|
268
|
+
`pi-subagents` SemVer `>=0.76.1`, with no upper bound, and the unchanged strict launch contract.
|
|
269
|
+
Older, prerelease, invalid or contract-incompatible builds fail closed with `reader-unavailable`.
|
|
270
|
+
The reader was verified with `0.76.1` on Pi `1.1.0`; Pi `0.87.1`, the declared minimum, is not
|
|
271
|
+
verified for the reader. Since `0.19.0`,
|
|
270
272
|
the reader also accepts the exact `@gotgenes/pi-permission-system` `33.0.3` runtime that a host adds for
|
|
271
273
|
permission rules; the host owns those rules, and the reader never reads or changes them. Delegation
|
|
272
274
|
`off` suspends the layer.
|
package/SECURITY.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Scope
|
|
4
4
|
|
|
5
|
-
This project is a local Pi extension. It stores delegation policy data, model identifiers, and any configured per-role thinking policy in global defaults and session entries; it does not store the thinking level chosen for an individual run. ContextShunt is off by default. When explicitly enforced, it may keep a known successful text result in a private, session-only temporary file to serve bounded recovery; the file has an opaque ID, quota, cancellation check, and an absolute 30-minute TTL from creation. Cleanup is scheduled while the process is active and runs at shutdown, but crashes or OS suspension can delay deletion. It does not store credentials and makes no network request of its own; when one of its explicit tools asks an external executor to answer a bounded question, that executor performs the call. The reader
|
|
5
|
+
This project is a local Pi extension. It stores delegation policy data, model identifiers, and any configured per-role thinking policy in global defaults and session entries; it does not store the thinking level chosen for an individual run. ContextShunt is off by default. When explicitly enforced, it may keep a known successful text result in a private, session-only temporary file to serve bounded recovery; the file has an opaque ID, quota, cancellation check, and an absolute 30-minute TTL from creation. Cleanup is scheduled while the process is active and runs at shutdown, but crashes or OS suspension can delay deletion. It does not store credentials and makes no network request of its own; when one of its explicit tools asks an external executor to answer a bounded question, that executor performs the call. The reader requires a stable `pi-subagents` SemVer version `>=0.76.1`, with no upper bound; older versions, prereleases and invalid versions fail closed before a request is sent. Every launch must still pass the strict contract checks; the version floor is not a guarantee of future-build compatibility. From `0.19.0`, the reader also admits the exact `@gotgenes/pi-permission-system` `33.0.3` runtime when the host adds it to the reader's child. The host owns its permission rules; the reader never reads or changes them, and an unverified permission entry fails closed. That runtime is trusted host code, not authority granted to the model. The check is an exact compatibility check, not a support guarantee. Versions `0.18.0` and earlier reject the extra runtime. From `0.20.0`, an enabled Advisor may be launched by the main agent with a forked context while the session fits in half of the Advisor model's window; the host then sends the conversation so far, including file contents and command output, to the Advisor's provider. Turn the Advisor off for sessions that must not reach that provider. The limits and privacy page of the wiki describes the details.
|
|
6
6
|
|
|
7
7
|
ContextShunt is not a sandbox or a worker bridge. It preserves permissions and backends, does not inspect files before tool authorization, and leaves errors, structured/mixed results, images, binaries, invalid inputs, and unknown contracts unchanged. One-time exceptions are user-authorized, short-lived, bound to one call and immutable input snapshot, and capped by declared lines plus real returned UTF-8 bytes. The policy guides the main agent. It cannot guarantee that another system will follow a configured role or thinking choice. Review local configuration before using it.
|
|
8
8
|
|
|
@@ -15,8 +15,9 @@ mode with an enabled valid advisor, never from hooks. Model input cannot choose
|
|
|
15
15
|
path, tools or extensions. Fork sends the conversation to each advisor provider; fresh sends the
|
|
16
16
|
question and brief. Disable Advisor when session content must not reach those providers.
|
|
17
17
|
|
|
18
|
-
The tool
|
|
19
|
-
|
|
18
|
+
The tool requires stable `pi-subagents` SemVer `>=0.76.1`, with no upper bound, rejecting
|
|
19
|
+
prereleases and invalid versions. It retains strict public preflight and terminal binding,
|
|
20
|
+
exact models/thinking, the packaged
|
|
20
21
|
no-tools profile, replace prompt, no project resources, and the reader's prompt-only or pinned
|
|
21
22
|
permission-runtime checks. Host rules remain host-owned. Forked conversation is
|
|
22
23
|
untrusted data, not authority. Preflight binds the tool session identity; the public REQUEST API cannot
|
|
@@ -68,4 +69,4 @@ Include the affected version or commit, operating system, Pi version, reproducti
|
|
|
68
69
|
|
|
69
70
|
## Supported versions
|
|
70
71
|
|
|
71
|
-
Only the latest published version is supported. Version `0.23.
|
|
72
|
+
Only the latest published version is supported. Version `0.23.2` requires Pi `0.87.1` or later; version `0.14.1` requires Pi `0.84.3` or later.
|
package/package.json
CHANGED
package/src/advisor-executor.ts
CHANGED
|
@@ -22,13 +22,6 @@ import {
|
|
|
22
22
|
ANTHROPIC_ADVISOR_EXTENSION,
|
|
23
23
|
} from "./advisor-anthropic-provider.ts";
|
|
24
24
|
|
|
25
|
-
/**
|
|
26
|
-
* `pi-subagents` builds whose launch contract was checked against this profile: the original pin and
|
|
27
|
-
* the build that only changed its own package version (same contract version, profile projection,
|
|
28
|
-
* tool plan, runtime lists and fork wrapper). A different build is rejected before any request.
|
|
29
|
-
*/
|
|
30
|
-
const ADMITTED_SUBAGENTS_VERSIONS: readonly string[] = ["0.76.1", "0.83.1"];
|
|
31
|
-
|
|
32
25
|
export const ADVISOR_AGENT_NAME = "pi-delegation-policy.advisor";
|
|
33
26
|
export type AdvisorRequest = AgentLaunchRequest & {
|
|
34
27
|
question: string;
|
|
@@ -46,7 +39,6 @@ const DEFINITION: AgentLaunchDefinition<AdvisorRequest, string> = {
|
|
|
46
39
|
agentPath: resolve(
|
|
47
40
|
fileURLToPath(new URL("../agents/pi-delegation-policy.advisor.md", import.meta.url)),
|
|
48
41
|
),
|
|
49
|
-
admittedProtocolVersions: ADMITTED_SUBAGENTS_VERSIONS,
|
|
50
42
|
allowFork: true,
|
|
51
43
|
internalTools: [],
|
|
52
44
|
buildTask: (request) =>
|
package/src/agent-launch.ts
CHANGED
|
@@ -16,6 +16,10 @@ const PACKAGE_MANIFEST_SEARCH_DEPTH = 3;
|
|
|
16
16
|
/** `pi-subagents` 0.76.1 `src/api/preflight.ts` and `src/shared/launch-contract.ts`. */
|
|
17
17
|
const LAUNCH_CONTRACT_VERSION = 3;
|
|
18
18
|
const DEFINITION_PROJECTION_VERSION = 2;
|
|
19
|
+
const MIN_SUBAGENTS_VERSION = [0n, 76n, 1n] as const;
|
|
20
|
+
// Stable SemVer only: build metadata does not affect precedence; prereleases are not admitted.
|
|
21
|
+
const STABLE_SEMVER =
|
|
22
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
19
23
|
const SHA256_LOWER_HEX = /^[0-9a-f]{64}$/;
|
|
20
24
|
const DIAGNOSTIC_SEVERITIES = new Set(["warning", "host-required"]);
|
|
21
25
|
const DELEGATION_EVENTS = {
|
|
@@ -148,12 +152,6 @@ export type AgentLaunchDefinition<Request extends AgentLaunchRequest, Value> = R
|
|
|
148
152
|
agentPath: string;
|
|
149
153
|
/** Only the advisor may send a fork; reader requests remain fresh-only. */
|
|
150
154
|
allowFork?: boolean;
|
|
151
|
-
/**
|
|
152
|
-
* The `pi-subagents` package versions whose launch contract this profile was verified against.
|
|
153
|
-
* An exact, per-profile list: no range, wildcard or fallback, so a new build is rejected until
|
|
154
|
-
* its contract is checked.
|
|
155
|
-
*/
|
|
156
|
-
admittedProtocolVersions: readonly string[];
|
|
157
155
|
/**
|
|
158
156
|
* Builtin tools the child must expose. A profile that declares no tools and no
|
|
159
157
|
* extensions exposes exactly these, so the contract must report the same list
|
|
@@ -204,6 +202,19 @@ function exactStrings(value: unknown, expected: readonly string[]): boolean {
|
|
|
204
202
|
);
|
|
205
203
|
}
|
|
206
204
|
|
|
205
|
+
function isSupportedSubagentsVersion(value: unknown): boolean {
|
|
206
|
+
if (typeof value !== "string") return false;
|
|
207
|
+
const match = STABLE_SEMVER.exec(value);
|
|
208
|
+
if (!match || match[0] !== value) return false;
|
|
209
|
+
for (const [index, minimum] of MIN_SUBAGENTS_VERSION.entries()) {
|
|
210
|
+
const digits = match[index + 1];
|
|
211
|
+
if (digits === undefined) return false;
|
|
212
|
+
const component = BigInt(digits);
|
|
213
|
+
if (component !== minimum) return component > minimum;
|
|
214
|
+
}
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
|
|
207
218
|
function isLowerHexDigest(value: unknown): value is string {
|
|
208
219
|
return typeof value === "string" && SHA256_LOWER_HEX.test(value);
|
|
209
220
|
}
|
|
@@ -422,8 +433,7 @@ function validateLaunchContract<Request extends AgentLaunchRequest, Value>(
|
|
|
422
433
|
contract.version !== LAUNCH_CONTRACT_VERSION ||
|
|
423
434
|
!isRecord(contract.protocol) ||
|
|
424
435
|
contract.protocol.lifecycleArtifactVersion !== 3 ||
|
|
425
|
-
|
|
426
|
-
!definition.admittedProtocolVersions.includes(contract.protocol.packageVersion) ||
|
|
436
|
+
!isSupportedSubagentsVersion(contract.protocol.packageVersion) ||
|
|
427
437
|
contract.runId !== active.requestId ||
|
|
428
438
|
!isLowerHexDigest(contract.digest)
|
|
429
439
|
)
|
|
@@ -132,7 +132,8 @@ export class ContextShuntAdapter {
|
|
|
132
132
|
): Promise<{ text?: string; error?: string; range?: string }> {
|
|
133
133
|
if (settings.mode !== "enforce") {
|
|
134
134
|
return {
|
|
135
|
-
error:
|
|
135
|
+
error:
|
|
136
|
+
"ContextShunt recovery is unavailable while enforcement is off. Continue without recovery; do not ask the user to enable enforcement unless they raise it.",
|
|
136
137
|
};
|
|
137
138
|
}
|
|
138
139
|
const result = await this.artifacts.recover(request, settings.limits.targetedReadBytes);
|
|
@@ -19,7 +19,6 @@ import type { SourceSnapshot } from "./context-shunt.ts";
|
|
|
19
19
|
import type { ReaderAnswer, ReaderThinking } from "./context-shunt-reader.ts";
|
|
20
20
|
|
|
21
21
|
export const CONTEXT_SHUNT_READER_AGENT_NAME = "pi-delegation-policy.context-shunt-inline-reader";
|
|
22
|
-
export const CONTEXT_SHUNT_READER_PROTOCOL_VERSION = "0.76.1";
|
|
23
22
|
export const READER_PREFLIGHT_TIMEOUT_MS = LAUNCH_PREFLIGHT_TIMEOUT_MS;
|
|
24
23
|
export const READER_TERMINAL_TIMEOUT_MS = LAUNCH_TERMINAL_TIMEOUT_MS;
|
|
25
24
|
|
|
@@ -109,7 +108,6 @@ function isReaderAnswer(value: unknown): value is ReaderAnswer {
|
|
|
109
108
|
const READER_LAUNCH = {
|
|
110
109
|
agentName: CONTEXT_SHUNT_READER_AGENT_NAME,
|
|
111
110
|
agentPath: CONTEXT_SHUNT_READER_AGENT_PATH,
|
|
112
|
-
admittedProtocolVersions: [CONTEXT_SHUNT_READER_PROTOCOL_VERSION],
|
|
113
111
|
internalTools: ["structured_output"],
|
|
114
112
|
buildTask: buildReaderTask,
|
|
115
113
|
result: {
|
package/src/context-shunt.ts
CHANGED
|
@@ -393,19 +393,26 @@ export class ArtifactStore {
|
|
|
393
393
|
|
|
394
394
|
async recover(request: RecoveryRequest, maxRangeBytes: number): Promise<RecoveryResult> {
|
|
395
395
|
return this.withLock(async () => {
|
|
396
|
-
|
|
396
|
+
const unavailable = {
|
|
397
|
+
error:
|
|
398
|
+
"Recovery artifact is unavailable or expired. Do not retry this artifact ID; use a new live artifact from an authorized bounded read if the evidence is still needed.",
|
|
399
|
+
};
|
|
400
|
+
if (this.closed) return unavailable;
|
|
397
401
|
const hasLines = request.lineOffset !== undefined || request.lineLimit !== undefined;
|
|
398
402
|
const hasBytes = request.byteOffset !== undefined || request.maxBytes !== undefined;
|
|
399
|
-
if (hasLines === hasBytes)
|
|
403
|
+
if (hasLines === hasBytes)
|
|
404
|
+
return {
|
|
405
|
+
error:
|
|
406
|
+
"Choose either a line range or a byte range. Call again with lineOffset/lineLimit or byteOffset/maxBytes, not both.",
|
|
407
|
+
};
|
|
400
408
|
|
|
401
409
|
await this.purgeUnlocked();
|
|
402
410
|
const artifact = this.artifacts.get(request.artifactId);
|
|
403
|
-
if (!artifact) return
|
|
411
|
+
if (!artifact) return unavailable;
|
|
404
412
|
if (artifact.kind === "derived") {
|
|
405
413
|
if (!artifact.sourceId || !(await this.readSourceUnlocked(artifact.sourceId)))
|
|
406
|
-
return
|
|
407
|
-
if (!this.artifacts.has(request.artifactId))
|
|
408
|
-
return { error: "Recovery artifact is unavailable or expired." };
|
|
414
|
+
return unavailable;
|
|
415
|
+
if (!this.artifacts.has(request.artifactId)) return unavailable;
|
|
409
416
|
}
|
|
410
417
|
|
|
411
418
|
let source: Buffer;
|
|
@@ -413,11 +420,11 @@ export class ArtifactStore {
|
|
|
413
420
|
source = await readFile(artifact.path);
|
|
414
421
|
} catch {
|
|
415
422
|
await this.remove(request.artifactId, artifact);
|
|
416
|
-
return
|
|
423
|
+
return unavailable;
|
|
417
424
|
}
|
|
418
425
|
if (digest(source) !== artifact.digest) {
|
|
419
426
|
await this.remove(request.artifactId, artifact);
|
|
420
|
-
return
|
|
427
|
+
return unavailable;
|
|
421
428
|
}
|
|
422
429
|
|
|
423
430
|
if (hasBytes) {
|
|
@@ -431,11 +438,16 @@ export class ArtifactStore {
|
|
|
431
438
|
offset > source.length ||
|
|
432
439
|
offset > Number.MAX_SAFE_INTEGER - length
|
|
433
440
|
) {
|
|
434
|
-
return {
|
|
441
|
+
return {
|
|
442
|
+
error: `Invalid byte range. Call again with a nonnegative integer byteOffset within the artifact and positive integer maxBytes no greater than ${maxRangeBytes}.`,
|
|
443
|
+
};
|
|
435
444
|
}
|
|
436
445
|
const end = Math.min(source.length, offset + length);
|
|
437
446
|
if (!isUtf8Boundary(source, offset) || !isUtf8Boundary(source, end)) {
|
|
438
|
-
return {
|
|
447
|
+
return {
|
|
448
|
+
error:
|
|
449
|
+
"Byte ranges must start and end at UTF-8 character boundaries. Call again with adjusted byte boundaries or use a bounded line range.",
|
|
450
|
+
};
|
|
439
451
|
}
|
|
440
452
|
return {
|
|
441
453
|
text: source.subarray(offset, end).toString("utf8"),
|
|
@@ -446,10 +458,16 @@ export class ArtifactStore {
|
|
|
446
458
|
const offset = request.lineOffset ?? 0;
|
|
447
459
|
const limit = request.lineLimit ?? 1;
|
|
448
460
|
if (!Number.isSafeInteger(offset) || offset < 0 || !positiveInteger(limit) || limit > 250) {
|
|
449
|
-
return {
|
|
461
|
+
return {
|
|
462
|
+
error:
|
|
463
|
+
"Invalid line range. Call again with a nonnegative integer lineOffset and an integer lineLimit from 1 to 250.",
|
|
464
|
+
};
|
|
450
465
|
}
|
|
451
466
|
const lines = splitLines(source.toString("utf8"));
|
|
452
|
-
if (offset >= lines.length)
|
|
467
|
+
if (offset >= lines.length)
|
|
468
|
+
return {
|
|
469
|
+
error: `Invalid line range. Call again with lineOffset less than ${lines.length}.`,
|
|
470
|
+
};
|
|
453
471
|
const text = lines.slice(offset, offset + limit).join("");
|
|
454
472
|
if (Buffer.byteLength(text, "utf8") > maxRangeBytes) {
|
|
455
473
|
return { error: "Requested line range exceeds the byte budget; use byte recovery." };
|
package/src/index.ts
CHANGED
|
@@ -39,7 +39,12 @@ import {
|
|
|
39
39
|
readerToolError,
|
|
40
40
|
type ReaderThinking,
|
|
41
41
|
} from "./context-shunt-reader.ts";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
type AdvisorContext,
|
|
44
|
+
advisorRoleLines,
|
|
45
|
+
buildDelegationPolicy,
|
|
46
|
+
selectAdvisorContext,
|
|
47
|
+
} from "./prompt.ts";
|
|
43
48
|
import { updateProviderPolicy } from "./provider-policy.ts";
|
|
44
49
|
import {
|
|
45
50
|
advisorStatusLabel,
|
|
@@ -310,6 +315,20 @@ export function statusText(state: RuntimeState): string {
|
|
|
310
315
|
if (errors.length) details.push(`details=${errors.join("; ")}`);
|
|
311
316
|
return details.join(" | ");
|
|
312
317
|
}
|
|
318
|
+
export function advisorUnavailableText(state: RuntimeState): string {
|
|
319
|
+
const entryErrors = state.advisorEntries?.flatMap((entry) => entry.errors) ?? [];
|
|
320
|
+
const diagnostics = [
|
|
321
|
+
...(state.advisorErrors ?? []).filter((error) => !entryErrors.includes(error)),
|
|
322
|
+
...(state.advisorEntries ?? []).flatMap((entry) =>
|
|
323
|
+
entry.errors.map((error) => `Advisor ${entry.index + 1}: ${error}`),
|
|
324
|
+
),
|
|
325
|
+
];
|
|
326
|
+
const action = state.diagnostics.some((diagnostic) => diagnostic.scope === "advisor")
|
|
327
|
+
? "Continue without Advisor consultation. Tell the user that the saved Advisor configuration is invalid and must be saved again in /delegate. Do not retry this tool."
|
|
328
|
+
: "Continue without Advisor consultation. Do not retry with the same settings or ask the user to change them unless they raise it.";
|
|
329
|
+
return `Advisor unavailable: no valid Advisors.\n${diagnostics.join("\n")}\n${action}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
313
332
|
function notifyAppendFailure(ctx: ExtensionCommandContext, result: GuardedAppendResult): void {
|
|
314
333
|
ctx.ui.notify(
|
|
315
334
|
result === "guard-failed"
|
|
@@ -525,7 +544,7 @@ export function createPiDelegationPolicy(options: PiDelegationPolicyOptions = {}
|
|
|
525
544
|
name: "consult_advisor",
|
|
526
545
|
label: "Consult Advisor",
|
|
527
546
|
description:
|
|
528
|
-
"Consult the configured advisors after orientation before substantive work, when stuck, before changing approach, and before declaring a multi-step task done (ask for remaining risks, not approval). Skip short reactive steps and mechanics within a settled approach. Supply a self-contained brief for fresh context or a rejected fork. Advice only; may send the conversation to each provider and incur cost.",
|
|
547
|
+
"Use only when advisor-launch=tool; in role mode launch Advisor with the host subagent tool instead. Consult the configured advisors after orientation before substantive work, when stuck, before changing approach, and before declaring a multi-step task done (ask for remaining risks, not approval). Skip short reactive steps and mechanics within a settled approach. Supply a self-contained brief for fresh context or a rejected fork. Advice only; may send the conversation to each provider and incur cost.",
|
|
529
548
|
parameters: Type.Object(
|
|
530
549
|
{
|
|
531
550
|
question: Type.String({ minLength: 1 }),
|
|
@@ -540,22 +559,33 @@ export function createPiDelegationPolicy(options: PiDelegationPolicyOptions = {}
|
|
|
540
559
|
});
|
|
541
560
|
const state = await refreshRuntime(ctx);
|
|
542
561
|
const advisors = validAdvisors(state);
|
|
543
|
-
if (state.effective
|
|
562
|
+
if (!isAdvisorEnabled(state.effective))
|
|
544
563
|
return text(
|
|
545
|
-
"Advisor
|
|
564
|
+
"Advisor is off in /delegate for this session. Do not consult it and do not ask the user to enable it unless they raise it.",
|
|
565
|
+
);
|
|
566
|
+
if (!advisors.length) return text(advisorUnavailableText(state));
|
|
567
|
+
if (state.effective.advisorLaunch === "role")
|
|
568
|
+
return text(
|
|
569
|
+
`Advisor launch is "role": this tool is not used in role mode; do not call this tool again. Launch every listed Advisor with the host subagent tool, using each exact agent profile and model below, in the background, all listed Advisors with the same question in one call, as the delegation policy describes. Replace LEVEL with a supported thinking level chosen per run under that Advisor's thinking policy; a fixed level must not change.\n${advisorRoleLines(state)}`,
|
|
546
570
|
);
|
|
547
571
|
if (advisorExecutor.busy)
|
|
548
572
|
return text(
|
|
549
|
-
"Advisor busy: one consultation group is already in flight for this session.",
|
|
573
|
+
"Advisor busy: one consultation group is already in flight for this session. Wait for its result before starting another consultation; do not duplicate the pending group.",
|
|
574
|
+
);
|
|
575
|
+
if (!input.question.trim())
|
|
576
|
+
return text(
|
|
577
|
+
"Advisor error: question must not be blank. Call again with a specific question.",
|
|
550
578
|
);
|
|
551
|
-
if (!input.question.trim()) return text("Advisor error: question must not be blank.");
|
|
552
579
|
const context = resolveAdvisorContext(ctx, state);
|
|
553
580
|
if (context === "fresh" && !input.brief?.trim())
|
|
554
581
|
return text(
|
|
555
582
|
"Advisor error: fresh context requires brief. Supply a short self-contained objective, constraints, relevant facts, options, and uncertainty.",
|
|
556
583
|
);
|
|
557
584
|
const sessionId = currentSessionId(ctx);
|
|
558
|
-
if (!sessionId)
|
|
585
|
+
if (!sessionId)
|
|
586
|
+
return text(
|
|
587
|
+
"Advisor unavailable: host session identity is missing. Continue without Advisor consultation; do not retry until the host supplies a session identity.",
|
|
588
|
+
);
|
|
559
589
|
const results = await advisorExecutor.execute(
|
|
560
590
|
advisors.map((entry) => ({
|
|
561
591
|
question: input.question,
|
|
@@ -717,7 +747,8 @@ export function createPiDelegationPolicy(options: PiDelegationPolicyOptions = {}
|
|
|
717
747
|
const result = latestRuntime
|
|
718
748
|
? await shunt.recover(input, latestRuntime.effective.contextShunt)
|
|
719
749
|
: {
|
|
720
|
-
error:
|
|
750
|
+
error:
|
|
751
|
+
"ContextShunt recovery is unavailable until session state is initialized. Continue without recovery; retry only after the host initializes the session.",
|
|
721
752
|
};
|
|
722
753
|
return {
|
|
723
754
|
content: [
|
package/src/prompt.ts
CHANGED
|
@@ -261,14 +261,10 @@ export function buildPolicyPreview(
|
|
|
261
261
|
];
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
advisorContext: AdvisorContext = "fresh",
|
|
267
|
-
): string | undefined {
|
|
264
|
+
/** Shared with role-mode tool redirection so launch models and thinking stay identical. */
|
|
265
|
+
export function advisorRoleLines(state: RuntimeState): string {
|
|
268
266
|
const { effective } = state;
|
|
269
|
-
|
|
270
|
-
const advisors = advisorEnabled ? validAdvisors(state) : [];
|
|
271
|
-
const advisorRoleLine = advisors
|
|
267
|
+
return validAdvisors(state)
|
|
272
268
|
.map(({ index, setting }) => {
|
|
273
269
|
const thinking = roleThinking(setting.thinking);
|
|
274
270
|
const label =
|
|
@@ -277,6 +273,16 @@ export function buildDelegationPolicy(
|
|
|
277
273
|
- ${label}: ${formatReference(setting)}; exact model base: ${formatLaunchModel(setting)}; pi-subagents form: ${formatThinkingLaunchModel(setting, thinking)}; thinking policy: ${thinkingPolicyText(thinking)}; agent profile: ${promptString(advisorAgentName(setting))}${setting.providerExtension ? "; provider extension explicitly enabled: normal configuration, startup hook and account diagnostic command are trusted host code, not model tools. Never invoke the diagnostic automatically." : ""}`;
|
|
278
274
|
})
|
|
279
275
|
.join("");
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function buildDelegationPolicy(
|
|
279
|
+
state: RuntimeState,
|
|
280
|
+
advisorContext: AdvisorContext = "fresh",
|
|
281
|
+
): string | undefined {
|
|
282
|
+
const { effective } = state;
|
|
283
|
+
const advisorEnabled = isAdvisorEnabled(effective) && !hasAdvisorError(state);
|
|
284
|
+
const advisors = advisorEnabled ? validAdvisors(state) : [];
|
|
285
|
+
const advisorRoleLine = advisorEnabled ? advisorRoleLines(state) : "";
|
|
280
286
|
if (effective.intensity === "off" || hasRuntimeError(state)) {
|
|
281
287
|
if (!advisorRoleLine) return undefined;
|
|
282
288
|
return `<delegation_policy>
|