@piagent/platform 1.7.1 → 1.9.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/AGENTS.md +7 -0
- package/CHANGELOG.md +113 -1
- package/README.md +45 -70
- package/README.vi.md +34 -61
- package/architecture/layers.json +16 -3
- package/catalog/capabilities.json +14 -7
- package/catalog/claude-model-additions.json +42 -0
- package/docs/auto-delegation-policy.md +2 -0
- package/docs/command-reference-vietnamese.md +69 -91
- package/docs/context-window-policy.md +5 -5
- package/docs/distribution-standard.md +16 -19
- package/docs/en/architecture.md +9 -1
- package/docs/herdr-workflow.md +2 -3
- package/docs/languages.json +4 -4
- package/docs/managed-local-acceptance.md +403 -0
- package/docs/mcp-and-tools.md +5 -7
- package/docs/memory-policy.md +3 -3
- package/docs/model-options.md +22 -17
- package/docs/oauth-providers.md +1 -1
- package/docs/operator-manual-vietnamese.md +85 -130
- package/docs/package-architecture-notes.md +4 -4
- package/docs/piagent-core-improvement-plan.md +3 -5
- package/docs/piagent-core-plan-checkpoint-2026-09-06.json +2 -13
- package/docs/piagent-runtime-benchmark-execution-plan.md +10 -8
- package/docs/project-adapters.md +3 -3
- package/docs/project-onboarding.md +15 -16
- package/docs/publishing-for-teams.md +3 -1
- package/docs/quality-benchmark.md +2 -0
- package/docs/quickstart-vietnamese.md +24 -25
- package/docs/release-install-policy.md +16 -11
- package/docs/research/harness-next-evidence-review.md +7 -7
- package/docs/runtime-harness-standard.md +2 -0
- package/docs/runtime-policy-design.md +2 -0
- package/docs/runtime-quality-baseline.md +2 -0
- package/docs/subagent-orchestration-capabilities.md +1 -1
- package/docs/subagents-and-multiagent.md +10 -8
- package/docs/task-implementation-contract.md +2 -0
- package/docs/task-lifecycle-vietnamese.md +2 -0
- package/docs/team-onboarding.md +33 -64
- package/docs/usage-observability.md +2 -2
- package/docs/vi/architecture.md +9 -1
- package/docs/workflow-recipes.md +6 -10
- package/docs-site/architecture.html +4 -3
- package/docs-site/assets/docs.css +80 -0
- package/docs-site/benchmark.html +7 -1
- package/docs-site/commands.html +37 -23
- package/docs-site/content/architecture.html +2 -2
- package/docs-site/content/benchmark.html +5 -0
- package/docs-site/content/commands.html +35 -22
- package/docs-site/content/ecosystem.html +144 -0
- package/docs-site/content/en/architecture.html +1 -1
- package/docs-site/content/en/benchmark.html +5 -0
- package/docs-site/content/en/commands.html +30 -13
- package/docs-site/content/en/ecosystem.html +144 -0
- package/docs-site/content/en/index.html +24 -9
- package/docs-site/content/en/quickstart.html +10 -12
- package/docs-site/content/en/runtime.html +1 -1
- package/docs-site/content/en/security.html +1 -1
- package/docs-site/content/en/team.html +2 -2
- package/docs-site/content/en/whats-new.html +89 -1
- package/docs-site/content/en/workflows.html +29 -31
- package/docs-site/content/index.html +24 -9
- package/docs-site/content/quickstart.html +10 -12
- package/docs-site/content/runtime.html +2 -2
- package/docs-site/content/security.html +2 -2
- package/docs-site/content/team.html +2 -2
- package/docs-site/content/whats-new.html +89 -1
- package/docs-site/content/workflows.html +36 -38
- package/docs-site/context-engine.html +2 -1
- package/docs-site/ecosystem.html +283 -0
- package/docs-site/en/architecture.html +3 -2
- package/docs-site/en/benchmark.html +7 -1
- package/docs-site/en/commands.html +32 -14
- package/docs-site/en/context-engine.html +2 -1
- package/docs-site/en/ecosystem.html +283 -0
- package/docs-site/en/index.html +30 -14
- package/docs-site/en/mcp-approval.html +2 -1
- package/docs-site/en/mcp-auth.html +2 -1
- package/docs-site/en/mcp-servers.html +2 -1
- package/docs-site/en/mcp.html +2 -1
- package/docs-site/en/permissions.html +2 -1
- package/docs-site/en/profiles.html +2 -1
- package/docs-site/en/quickstart.html +17 -18
- package/docs-site/en/runtime.html +3 -2
- package/docs-site/en/scope.html +2 -1
- package/docs-site/en/security.html +6 -5
- package/docs-site/en/team.html +4 -3
- package/docs-site/en/whats-new.html +97 -8
- package/docs-site/en/workflows.html +34 -35
- package/docs-site/index.html +30 -14
- package/docs-site/mcp-approval.html +2 -1
- package/docs-site/mcp-auth.html +2 -1
- package/docs-site/mcp-servers.html +2 -1
- package/docs-site/mcp.html +2 -1
- package/docs-site/permissions.html +2 -1
- package/docs-site/profiles.html +2 -1
- package/docs-site/quickstart.html +17 -18
- package/docs-site/runtime.html +4 -3
- package/docs-site/scope.html +2 -1
- package/docs-site/security.html +7 -6
- package/docs-site/team.html +4 -3
- package/docs-site/whats-new.html +97 -8
- package/docs-site/workflows.html +41 -42
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/private-holdout-v1/CURRENT_BOUNDARY.md +17 -0
- package/evals/private-holdout-v1/public-exposure.v2.json +2 -2
- package/package.json +10 -9
- package/packages/piagent-core/README.md +15 -23
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +2 -0
- package/packages/piagent-core/extensions/experiment-loop-policy.ts +75 -0
- package/packages/piagent-core/extensions/local-state-path.js +15 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +108 -960
- package/packages/piagent-core/extensions/policy-core.js +2 -2
- package/packages/piagent-core/extensions/source-checkout-paths.ts +54 -0
- package/packages/piagent-core/managed/broker-client.mjs +49 -0
- package/packages/piagent-core/managed/capabilities.mjs +28 -0
- package/packages/piagent-core/managed/compaction.mjs +63 -0
- package/packages/piagent-core/managed/doctor.mjs +107 -0
- package/packages/piagent-core/managed/native-catalog.mjs +42 -0
- package/packages/piagent-core/managed/patch-snapshot.mjs +48 -0
- package/packages/piagent-core/managed/repository-operation.mjs +84 -0
- package/packages/piagent-core/managed/request-stream.mjs +129 -0
- package/packages/piagent-core/managed/resource-loader.mjs +42 -0
- package/packages/piagent-core/managed/review-snapshot-worker.mjs +27 -0
- package/packages/piagent-core/managed/sandbox-diagnostic.mjs +12 -0
- package/packages/piagent-core/managed/search-fallback.mjs +93 -0
- package/packages/piagent-core/managed/session.mjs +472 -0
- package/packages/piagent-core/managed/store.d.mts +3 -0
- package/packages/piagent-core/managed/store.mjs +64 -0
- package/packages/piagent-core/managed/tool-boundary.mjs +243 -0
- package/packages/piagent-core/managed/tool-worker.mjs +36 -0
- package/packages/piagent-core/managed/toolchain.mjs +120 -0
- package/packages/piagent-core/managed/web-fetch.mjs +105 -0
- package/packages/piagent-core/managed/web-search.mjs +96 -0
- package/packages/piagent-core/managed/workflow.mjs +299 -0
- package/packages/piagent-core/package.json +3 -6
- package/packages/piagent-core/policies/base-policy.json +80 -26
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +3 -3
- package/packages/piagent-core/runtime/hooks/input-hook.ts +35 -196
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +2 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +9 -1
- package/packages/piagent-core/runtime/inspection/same-process-bridge-proof.ts +1 -1
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +1 -1
- package/packages/piagent-core/runtime/managed-failure.d.mts +10 -0
- package/packages/piagent-core/runtime/managed-failure.mjs +164 -0
- package/packages/piagent-core/runtime/model/model-route-policy.ts +33 -13
- package/packages/piagent-core/runtime/model/model-route-types.ts +10 -6
- package/packages/piagent-core/runtime/orchestration/minimal-delegation-policy.ts +8 -29
- package/packages/piagent-core/runtime/orchestration/owned-work-budget.ts +1 -1
- package/packages/piagent-core/runtime/policy/block-remedy.ts +5 -3
- package/packages/piagent-core/runtime/registration/context-commands.ts +1 -1
- package/packages/piagent-core/runtime/registration/knowledge-tools.ts +7 -7
- package/packages/piagent-core/runtime/registration/policy-tools.ts +2 -2
- package/packages/piagent-core/runtime/registration/session-commands.ts +11 -26
- package/packages/piagent-core/runtime/registration/task-evidence-tools.ts +1 -1
- package/packages/piagent-core/runtime/session/system-prompt.ts +6 -7
- package/packages/piagent-core/runtime/workflows/webui-runtime-command.ts +6 -6
- package/packages/piagent-core/runtime/workflows/webui-workflow.ts +6 -11
- package/packages/piagent-core/security/sensitive-text.js +9 -0
- package/packages/piagent-core/skills/piagent-ops/SKILL.md +8 -10
- package/packages/piagent-core/skills/test-audit/SKILL.md +101 -0
- package/packages/piagent-core/subagents/piagent-planner.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-DGk34zJT.js → MarkdownMessage-Dd9wPk-v.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-7I-50Q2U.js +228 -0
- package/packages/piagent-webui/dist/client/assets/{index-DVahUVWy.css → index-DgSmcnP8.css} +1 -1
- package/packages/piagent-webui/dist/client/index.html +2 -2
- package/packages/piagent-webui/extension/piagent-webui.ts +11 -11
- package/packages/piagent-webui/extension/session-stream-adapter.ts +3 -1
- package/packages/piagent-webui/extension/terminal-session-adapter.ts +6 -1
- package/packages/piagent-webui/gateway/company-relay.ts +308 -0
- package/packages/piagent-webui/gateway/control-socket.ts +39 -12
- package/packages/piagent-webui/gateway/gateway-service.ts +109 -28
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +58 -2
- package/packages/piagent-webui/gateway/managed-gateway.mjs +41 -0
- package/packages/piagent-webui/gateway/managed-launch.ts +145 -0
- package/packages/piagent-webui/gateway/managed-projection.ts +20 -0
- package/packages/piagent-webui/gateway/pi-host.ts +22 -4
- package/packages/piagent-webui/gateway/runtime-session-controls.ts +5 -2
- package/packages/piagent-webui/gateway/session-approval-authority.ts +28 -0
- package/packages/piagent-webui/gateway/session-catalog.ts +11 -3
- package/packages/piagent-webui/gateway/session-command-controller.ts +8 -2
- package/packages/piagent-webui/gateway/session-file-facts.ts +25 -0
- package/packages/piagent-webui/gateway/session-inspection-registry.ts +42 -12
- package/packages/piagent-webui/gateway/session-list-cache.ts +68 -0
- package/packages/piagent-webui/gateway/session-runtime-ownership.ts +8 -1
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +20 -27
- package/packages/piagent-webui/ownership/managed-terminal-lease.ts +27 -0
- package/packages/piagent-webui/ownership/profile-state.ts +2 -0
- package/packages/piagent-webui/ownership/session-lease-store.ts +22 -1
- package/packages/piagent-webui/server/approval-projection.ts +9 -0
- package/packages/piagent-webui/server/core-inspection-provider.ts +4 -9
- package/packages/piagent-webui/server/loopback-server.ts +37 -1
- package/packages/piagent-webui/server/message-stop-reason.ts +11 -0
- package/packages/piagent-webui/server/tool-call-summary.ts +185 -0
- package/packages/piagent-webui/server/transcript-projection.ts +86 -3
- package/packs/engineering-base/pack.json +4 -0
- package/schemas/piagent-webui/runtime-command-v1.schema.json +1 -2
- package/schemas/piagent-webui/session-catalog-v1.schema.json +8 -0
- package/schemas/piagent-webui/transcript-v1.schema.json +94 -1
- package/scripts/build-docs-site.mjs +23 -12
- package/scripts/check-public-wording.mjs +5 -6
- package/scripts/check-runtime-advisories.mjs +23 -6
- package/scripts/claude-model-additions.mjs +33 -0
- package/scripts/configure-model-scope.sh +36 -8
- package/scripts/configure-subagents.sh +1 -1
- package/scripts/explain-command.mjs +5 -6
- package/scripts/install-global.sh +2 -2
- package/scripts/model-route-evaluation-core.ts +1 -0
- package/scripts/model-scope-prune.mjs +75 -0
- package/scripts/phase-tools-evaluation.mjs +9 -4
- package/scripts/pi-model-route.mjs +1 -1
- package/scripts/piagent-cli.mjs +51 -2
- package/scripts/piagent-dashboard.mjs +27 -4
- package/scripts/piagent-installations.mjs +26 -0
- package/scripts/piagent-studio.mjs +155 -0
- package/scripts/runtime-advisory-exceptions.json +29 -0
- package/scripts/setup.sh +2 -2
- package/scripts/team-doctor.sh +20 -1
- package/scripts/typescript-loader.mjs +6 -0
- package/scripts/verify-local.sh +5 -5
- package/scripts/verify-release-identity.mjs +1 -1
- package/templates/global/settings.json +8 -2
- package/templates/project/.pi/project-context.md +3 -3
- package/templates/project/AGENTS.md +4 -5
- package/docs/piagent-core-reasoning-research-2026-09-06.md +0 -225
- package/docs/piagent-core-research-sources-2026-09-06.json +0 -416
- package/packages/piagent-webui/dist/client/assets/index-DiKRmCEP.js +0 -176
|
@@ -5,34 +5,12 @@ import { matchesProtectedPath } from "../../extensions/policy-core.js";
|
|
|
5
5
|
import type { TaskContract } from "../../extensions/guard-types.js";
|
|
6
6
|
import { attachLocalImagesFromText, extractLocalImagePathCandidates } from "../input/chat-images.ts";
|
|
7
7
|
import type { ChatImageAccessPolicy } from "../input/chat-images.ts";
|
|
8
|
-
import { LONG_INPUT_CHARS } from "../runtime-limits.ts";
|
|
9
8
|
import type { AuthorityResumeDecision } from "../policy/authority-resume-policy.ts";
|
|
10
|
-
import { buildHandoffProjection, writeHandoffProjection } from "../recovery/handoff-projection.ts";
|
|
11
|
-
import {
|
|
12
|
-
isUncertainSendContinuation,
|
|
13
|
-
terminalUncertainSendReceipt
|
|
14
|
-
} from "../session/uncertain-send-continuation.ts";
|
|
15
|
-
import { buildContextPreflight, buildUsageSnapshot } from "../session/usage.ts";
|
|
16
9
|
import { registerAdaptiveContextGovernor } from "../session/adaptive-context-governor.ts";
|
|
17
|
-
import {
|
|
10
|
+
import { toolGroupsForPrompt, PIAGENT_TOOL_NAMES, PIAGENT_TOOL_ORDER, PIAGENT_TOOL_GROUPS } from "../tools/tool-groups.ts";
|
|
18
11
|
import type { PiagentToolGroup } from "../tools/tool-groups.ts";
|
|
19
12
|
import type { RuntimeSessionState } from "../session/runtime-state.ts";
|
|
20
13
|
import { observeHostWireInput } from "../session/runtime-state.ts";
|
|
21
|
-
import {
|
|
22
|
-
buildFreshCommand,
|
|
23
|
-
agentStartTaskRequest,
|
|
24
|
-
chooseFreshWorkflow,
|
|
25
|
-
extractTaskRequest,
|
|
26
|
-
isFreshOrUtilityInput,
|
|
27
|
-
isPiagentWorkflowInput,
|
|
28
|
-
looksLikeGovernedBoilerplate,
|
|
29
|
-
trimTaskForInline
|
|
30
|
-
} from "../workflows/input-routing.ts";
|
|
31
|
-
import {
|
|
32
|
-
automaticTaskIntakeMode,
|
|
33
|
-
isLightweightNonAuthorizingChangeContinuation,
|
|
34
|
-
manualTaskIntakeEligible
|
|
35
|
-
} from "../workflows/task-intake.ts";
|
|
36
14
|
|
|
37
15
|
type InputHookDependencies = {
|
|
38
16
|
state: RuntimeSessionState;
|
|
@@ -54,191 +32,52 @@ export function projectPiagentWireInput(input: {
|
|
|
54
32
|
const blocked = (reason: string) => ({ disposition: "blocked", reason });
|
|
55
33
|
if (input.activeTask === undefined || !Array.isArray(input.readProtectedPaths)
|
|
56
34
|
|| typeof input.replacementIntake !== "boolean" || typeof input.dynamicToolsEnabled !== "boolean") return blocked("input-state-unfrozen");
|
|
57
|
-
const text = input.text
|
|
35
|
+
const text = input.text;
|
|
58
36
|
if (input.hasImages !== false || extractLocalImagePathCandidates(text, "/").length > 0) return blocked("input-images-unprojected");
|
|
59
|
-
if (!text
|
|
37
|
+
if (!text.trim()) return blocked("input-empty");
|
|
60
38
|
if (!["rpc", "interactive", "extension"].includes(input.source)) return blocked("input-source-unfrozen");
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
const onlyProtected = signal.paths.length > 0 && signal.paths.every((p) => matchesProtectedPath(p, input.readProtectedPaths));
|
|
65
|
-
const runtimeMode = !activeTask ? automaticTaskIntakeMode(text, input.readProtectedPaths) : undefined;
|
|
66
|
-
const manual = !activeTask && !runtimeMode && manualTaskIntakeEligible(text, input.readProtectedPaths);
|
|
67
|
-
const groups = onlyProtected ? [] : toolGroupsForPrompt(text).filter((group) => !runtimeMode || group !== "intake" && group !== "task");
|
|
68
|
-
if (manual && !groups.includes("intake")) groups.push("intake");
|
|
69
|
-
const selectedGroups = activeTask ? [...groups.filter((group) => input.replacementIntake || group !== "intake"),
|
|
70
|
-
...(input.replacementIntake ? ["intake" as const] : activeTaskToolGroups(activeTask))] : groups;
|
|
39
|
+
const signal = classifyContextTask(text);
|
|
40
|
+
const protectedOnly = signal.paths.length > 0 && signal.paths.every((p) => matchesProtectedPath(p, input.readProtectedPaths));
|
|
41
|
+
const groups = protectedOnly ? [] : toolGroupsForPrompt(text).filter((g) => !["intake", "task", "recovery"].includes(g));
|
|
71
42
|
const builtins = input.currentTools.filter((name) => !PIAGENT_TOOL_NAMES.has(name));
|
|
72
43
|
if (builtins.includes("apply_patch")) {
|
|
73
|
-
const
|
|
74
|
-
if (
|
|
44
|
+
const writer = builtins.findIndex((n) => n === "edit" || n === "write");
|
|
45
|
+
if (writer >= 0) { builtins.splice(builtins.indexOf("apply_patch"), 1); builtins.splice(builtins.findIndex((n) => n === "edit" || n === "write"), 0, "apply_patch"); }
|
|
75
46
|
}
|
|
76
47
|
const selectedTools = input.dynamicToolsEnabled ? [...builtins, ...PIAGENT_TOOL_ORDER.filter((name) =>
|
|
77
|
-
|
|
48
|
+
groups.some((group) => PIAGENT_TOOL_GROUPS[group].includes(name as never)))] : [...input.currentTools];
|
|
78
49
|
if (selectedTools.some((name) => !input.availableToolNames.includes(name))) return blocked("tool-definition-missing");
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const agentStartRuntimeMode = !activeTask ? automaticTaskIntakeMode(query, input.readProtectedPaths) : undefined;
|
|
82
|
-
if (runtimeMode || agentStartRuntimeMode || input.replacementIntake) return blocked("automatic-task-state-unprojected");
|
|
83
|
-
if (activeTask && !input.phase) return blocked("current-task-phase-unfrozen");
|
|
84
|
-
return { disposition: "known", reason: null, groups: selectedGroups, selectedTools,
|
|
85
|
-
compactMode: protectedQuery ? "protected" : activeTask?.intakeMode === "runtime" ? "automatic" : null,
|
|
86
|
-
taskPresence: activeTask ? "current" : "none", phase: activeTask ? input.phase : null,
|
|
50
|
+
return { disposition: "known", reason: null, groups, selectedTools,
|
|
51
|
+
compactMode: protectedOnly ? "protected" : null, taskPresence: "none", phase: null,
|
|
87
52
|
inputHash: signal.promptHash, inputText: text };
|
|
53
|
+
|
|
88
54
|
}
|
|
89
55
|
|
|
90
56
|
export function registerInputHook(pi: ExtensionAPI, dependencies: InputHookDependencies): void {
|
|
91
|
-
registerAdaptiveContextGovernor(pi, {
|
|
92
|
-
activeTask: dependencies.activeTask,
|
|
93
|
-
telemetry: dependencies.telemetry
|
|
94
|
-
});
|
|
95
|
-
|
|
57
|
+
registerAdaptiveContextGovernor(pi, { activeTask: () => undefined, telemetry: dependencies.telemetry });
|
|
96
58
|
pi.on("input", async (event, ctx) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
? terminalUncertainSendReceipt(ctx.cwd, sessionTask)
|
|
109
|
-
: undefined;
|
|
110
|
-
if (terminalReceipt && event.source !== "extension") {
|
|
111
|
-
dependencies.telemetry(ctx, {
|
|
112
|
-
event: "uncertain_send_terminal_receipt_reemitted",
|
|
113
|
-
source: event.source,
|
|
114
|
-
promptHash: taskSignal.promptHash,
|
|
115
|
-
taskId: terminalReceipt.details.taskId,
|
|
116
|
-
taskRunId: terminalReceipt.details.taskRunId,
|
|
117
|
-
outcome: terminalReceipt.details.outcome,
|
|
118
|
-
completionApproved: terminalReceipt.details.completionApproved,
|
|
119
|
-
replacementTaskStarted: false,
|
|
120
|
-
replayed: false,
|
|
121
|
-
modelTurnStarted: false
|
|
122
|
-
});
|
|
123
|
-
pi.sendMessage(
|
|
124
|
-
{
|
|
125
|
-
customType: terminalReceipt.customType,
|
|
126
|
-
content: terminalReceipt.content,
|
|
127
|
-
display: true,
|
|
128
|
-
details: terminalReceipt.details
|
|
129
|
-
},
|
|
130
|
-
{ triggerTurn: false }
|
|
131
|
-
);
|
|
132
|
-
return { action: "handled" };
|
|
133
|
-
}
|
|
134
|
-
if (!uncertainSendContinuation) {
|
|
135
|
-
if (sessionTask && sessionTask.trace.outcome !== "pending") dependencies.state.clearTaskBoundary(ctx, sessionTask.taskRunId);
|
|
136
|
-
else if (!sessionTask) {
|
|
137
|
-
const cachedTask = dependencies.state.taskIdentity(ctx);
|
|
138
|
-
if (cachedTask) dependencies.state.clearTaskBoundary(ctx, cachedTask.taskRunId);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
const activeTask = sessionTask?.trace.outcome === "pending" ? sessionTask : undefined;
|
|
142
|
-
const turn = dependencies.state.beginTurn(ctx, taskSignal.promptHash, {
|
|
143
|
-
lightweightNonAuthorizingChange: isLightweightNonAuthorizingChangeContinuation(text)
|
|
144
|
-
});
|
|
145
|
-
observeHostWireInput(ctx.cwd, ctx.sessionManager.getSessionId(), { turnId: turn.turnId,
|
|
146
|
-
promptHash: taskSignal.promptHash, text, source: event.source,
|
|
147
|
-
hasImages: Boolean(event.images?.length || extractLocalImagePathCandidates(text, ctx.cwd).length) });
|
|
148
|
-
const authorityPolicy = activeTask?.trace.outcome === "pending" ? dependencies.authorityPolicy(ctx, activeTask) : undefined;
|
|
149
|
-
let authorityHandoffReady = false;
|
|
150
|
-
if (activeTask && authorityPolicy?.disposition === "new-attempt-required") {
|
|
151
|
-
try {
|
|
152
|
-
writeHandoffProjection(ctx.cwd, buildHandoffProjection(ctx.cwd, activeTask, {
|
|
153
|
-
gate: { decision: "fail", missing: [`authority policy handoff: ${authorityPolicy.reason}`], missingVerifyCommands: [] },
|
|
154
|
-
recovery: null
|
|
155
|
-
}));
|
|
156
|
-
authorityHandoffReady = true;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
ctx.ui.notify(`Piagent authority-policy handoff could not be written: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
const replacementIntake = Boolean(activeTask && (
|
|
162
|
-
activeTask.workingTreeDigestMigration?.status === "new-attempt-required" || authorityHandoffReady
|
|
163
|
-
));
|
|
164
|
-
const readProtectedPaths = dependencies.readProtectedPaths(ctx);
|
|
165
|
-
const protectedTarget = taskSignal.paths.some((candidate) => matchesProtectedPath(candidate, readProtectedPaths));
|
|
166
|
-
const protectedOnlyTarget = taskSignal.paths.length > 0
|
|
167
|
-
&& taskSignal.paths.every((candidate) => matchesProtectedPath(candidate, readProtectedPaths));
|
|
168
|
-
const runtimeIntakeMode = !activeTask ? automaticTaskIntakeMode(text, readProtectedPaths) : undefined;
|
|
169
|
-
const runtimeIntake = Boolean(runtimeIntakeMode);
|
|
170
|
-
const manualIntake = !activeTask && !runtimeIntake && manualTaskIntakeEligible(text, readProtectedPaths);
|
|
171
|
-
const promptGroups = protectedOnlyTarget
|
|
172
|
-
? []
|
|
173
|
-
: toolGroupsForPrompt(text).filter((group) => (
|
|
174
|
-
runtimeIntake ? group !== "intake" && group !== "task" : true
|
|
175
|
-
));
|
|
176
|
-
if (manualIntake && !promptGroups.includes("intake")) promptGroups.push("intake");
|
|
177
|
-
dependencies.activateToolGroups(ctx, activeTask?.trace.outcome === "pending"
|
|
178
|
-
? [...promptGroups.filter((group) => replacementIntake || group !== "intake"), ...(replacementIntake ? ["intake" as const] : activeTaskToolGroups(activeTask))]
|
|
179
|
-
: promptGroups);
|
|
180
|
-
dependencies.telemetry(ctx, {
|
|
181
|
-
event: "user_input",
|
|
182
|
-
turnId: turn.turnId,
|
|
183
|
-
source: event.source,
|
|
184
|
-
promptHash: taskSignal.promptHash,
|
|
185
|
-
promptChars: taskSignal.promptChars,
|
|
186
|
-
workflow: taskSignal.workflow,
|
|
187
|
-
riskLane: taskSignal.lane,
|
|
188
|
-
intakeMode: runtimeIntake ? "runtime" : "model",
|
|
189
|
-
taskMode: runtimeIntakeMode,
|
|
190
|
-
protectedTarget,
|
|
191
|
-
protectedOnlyTarget,
|
|
192
|
-
manualIntake,
|
|
193
|
-
explicitPathCount: taskSignal.paths.length,
|
|
194
|
-
termCount: taskSignal.terms.length
|
|
59
|
+
// Preserve the actual message, including whitespace and slash-like text.
|
|
60
|
+
// Session/run lifecycle belongs to the host, not to prompt rewriting.
|
|
61
|
+
const text = event.text;
|
|
62
|
+
if (!text.trim()) return { action: "continue" };
|
|
63
|
+
const signal = classifyContextTask(text);
|
|
64
|
+
const cached = dependencies.state.taskIdentity(ctx);
|
|
65
|
+
if (cached) dependencies.state.clearTaskBoundary(ctx, cached.taskRunId);
|
|
66
|
+
const turn = dependencies.state.beginTurn(ctx, signal.promptHash);
|
|
67
|
+
observeHostWireInput(ctx.cwd, ctx.sessionManager.getSessionId(), {
|
|
68
|
+
turnId: turn.turnId, promptHash: signal.promptHash, text, source: event.source,
|
|
69
|
+
hasImages: Boolean(event.images?.length || extractLocalImagePathCandidates(text, ctx.cwd).length)
|
|
195
70
|
});
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
203
|
-
if (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const inputText = imageAttachment?.text ?? text;
|
|
210
|
-
const canRewriteWorkflow = event.source !== "extension";
|
|
211
|
-
const snapshot = buildUsageSnapshot(ctx, String(pi.getThinkingLevel()));
|
|
212
|
-
const preflight = buildContextPreflight(snapshot, chooseFreshWorkflow(inputText, inputText), inputText.length);
|
|
213
|
-
const hasBoilerplate = looksLikeGovernedBoilerplate(inputText);
|
|
214
|
-
const shouldFreshen = canRewriteWorkflow
|
|
215
|
-
&& preflight.recommendation === "fresh-session"
|
|
216
|
-
&& (hasBoilerplate || isPiagentWorkflowInput(inputText) || inputText.length >= LONG_INPUT_CHARS);
|
|
217
|
-
const shouldCollapseBoilerplate = canRewriteWorkflow
|
|
218
|
-
&& hasBoilerplate
|
|
219
|
-
&& inputText.length >= dependencies.boilerplateCollapseChars;
|
|
220
|
-
const outgoingImages = [
|
|
221
|
-
...(Array.isArray(event.images) ? event.images : []),
|
|
222
|
-
...(imageAttachment?.images ?? [])
|
|
223
|
-
];
|
|
224
|
-
|
|
225
|
-
if (!shouldFreshen && !shouldCollapseBoilerplate) {
|
|
226
|
-
if (imageAttachment?.attached.length) return { action: "transform", text: inputText, images: outgoingImages };
|
|
227
|
-
return { action: "continue" };
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
const task = extractTaskRequest(inputText);
|
|
231
|
-
const workflow = chooseFreshWorkflow(inputText, task);
|
|
232
|
-
const reason = shouldFreshen
|
|
233
|
-
? "Current session is near context limits; use a fresh governed session."
|
|
234
|
-
: "Mandatory flow boilerplate is already part of the platform; collapse it to the task request.";
|
|
235
|
-
const command = shouldFreshen
|
|
236
|
-
? buildFreshCommand(ctx.cwd, workflow, inputText, reason)
|
|
237
|
-
: `/${workflow} ${trimTaskForInline(task)}`;
|
|
238
|
-
|
|
239
|
-
ctx.ui.notify(`Piagent preflight: ${reason}`, "warning");
|
|
240
|
-
return outgoingImages.length > 0
|
|
241
|
-
? { action: "transform", text: command, images: outgoingImages }
|
|
242
|
-
: { action: "transform", text: command };
|
|
71
|
+
const protectedPaths = dependencies.readProtectedPaths(ctx);
|
|
72
|
+
const protectedOnly = signal.paths.length > 0 && signal.paths.every((p) => matchesProtectedPath(p, protectedPaths));
|
|
73
|
+
dependencies.activateToolGroups(ctx, protectedOnly ? [] : toolGroupsForPrompt(text)
|
|
74
|
+
.filter((group) => !["intake", "task", "recovery"].includes(group)));
|
|
75
|
+
dependencies.telemetry(ctx, { event: "user_input", turnId: turn.turnId, source: event.source,
|
|
76
|
+
promptHash: signal.promptHash, promptChars: text.length, inputMode: "freeform" });
|
|
77
|
+
const attachment = attachLocalImagesFromText(text, event.images, ctx.cwd, () => dependencies.imageAccess(ctx));
|
|
78
|
+
if (attachment?.attached.length) return { action: "transform", text: attachment.text,
|
|
79
|
+
images: [...(event.images ?? []), ...attachment.images] };
|
|
80
|
+
if (attachment?.skipped.length) ctx.ui.notify(`Skipped ${attachment.skipped.length} inaccessible image(s)`, "warning");
|
|
81
|
+
return { action: "continue" };
|
|
243
82
|
});
|
|
244
83
|
}
|
|
@@ -5,6 +5,7 @@ import { contextIndexV2Status } from "../../extensions/context-engine.js";
|
|
|
5
5
|
import { resolveExecutionBackend } from "../../extensions/execution-backend.js";
|
|
6
6
|
import type { ProjectProfile, TaskContract } from "../../extensions/guard-types.js";
|
|
7
7
|
import { pruneCaptureFiles } from "../../extensions/state-retention.js";
|
|
8
|
+
import { ensureStateRootIgnored } from "../../extensions/local-state-path.js";
|
|
8
9
|
import { migrateTaskState, readSessionTaskBinding } from "../../extensions/task-state.js";
|
|
9
10
|
import {
|
|
10
11
|
pruneTaskJournal
|
|
@@ -71,6 +72,7 @@ type SessionStartHookDependencies = {
|
|
|
71
72
|
|
|
72
73
|
export function registerSessionStartHook(pi: ExtensionAPI, dependencies: SessionStartHookDependencies): void {
|
|
73
74
|
pi.on("session_start", async (_event, ctx) => {
|
|
75
|
+
ensureStateRootIgnored(ctx.cwd);
|
|
74
76
|
dependencies.activateToolGroups(ctx, []);
|
|
75
77
|
const projectTrusted = ctx.isProjectTrusted();
|
|
76
78
|
const explicitProfile = Boolean(process.env.PIAGENT_PROFILE?.trim());
|
|
@@ -210,7 +210,15 @@ export class PiApprovalBroker {
|
|
|
210
210
|
#action(binding: Binding, draft: ApprovalActionDraft, cwd: string): Record<string, unknown> {
|
|
211
211
|
const command = draft.commandPreview === null || draft.commandPreview === undefined ? null : safe(draft.commandPreview, 4_000);
|
|
212
212
|
const parameter = safe(draft.parameterPreview ?? "No additional parameters", 4_000);
|
|
213
|
-
|
|
213
|
+
// Shown to the member: a path inside the project is written relative to it
|
|
214
|
+
// ("." is the project folder itself), so the card does not repeat the
|
|
215
|
+
// home directory; a path outside the project stays absolute.
|
|
216
|
+
const shown = (item: string) => {
|
|
217
|
+
if (!path.isAbsolute(item)) return item;
|
|
218
|
+
const relative = path.relative(cwd, item);
|
|
219
|
+
return relative === "" ? "." : !relative.startsWith("..") && !path.isAbsolute(relative) ? relative : item;
|
|
220
|
+
};
|
|
221
|
+
const targetPaths = (draft.targetPaths ?? []).slice(0, 32).map((item) => safe(shown(item), 500)).filter((item) => item.text).map((item) => item.text);
|
|
214
222
|
const targetSummaries = (draft.targetSummaries ?? []).slice(0, 32).map((item) => safe(item, 500)).filter((item) => item.text).map((item) => item.text);
|
|
215
223
|
const removed = [command, parameter].filter((item) => item?.redacted).length;
|
|
216
224
|
const truncated = Boolean(command?.truncated || parameter.truncated || (draft.targetPaths?.length ?? 0) > 32 || (draft.targetSummaries?.length ?? 0) > 32);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
export const PINNED_BRIDGE_HOST_VERSION = "0.
|
|
3
|
+
export const PINNED_BRIDGE_HOST_VERSION = "0.87.1" as const;
|
|
4
4
|
export const SAME_PROCESS_BRIDGE_PROOF_VERSION = "piagent-webui-same-process-bridge-v1" as const;
|
|
5
5
|
|
|
6
6
|
const HOST_EVENTS = Object.freeze([
|
|
@@ -248,7 +248,7 @@ function capabilities(identity: WebUiIdentity, generatedAt: string, resyncRequir
|
|
|
248
248
|
capabilities: {
|
|
249
249
|
inspect: { status: "available", version: 1, reason: null, sourceViews: ["task", "working-tree", "staged"] },
|
|
250
250
|
"control.chat": unavailableCapability("chat-control-not-enabled", "The same-session bridge exists, but transcript, queue, server control and browser UX are not enabled"),
|
|
251
|
-
"control.lifecycle": unavailableCapability("lifecycle-contract-incomplete", "Pi
|
|
251
|
+
"control.lifecycle": unavailableCapability("lifecycle-contract-incomplete", "Pi host lacks semantic Pause and an acknowledged phase-complete Stop contract"),
|
|
252
252
|
"control.resumeAndContinue": unavailableCapability("compound-control-unavailable", "Resume is unavailable until a durable acknowledged Pause contract exists"),
|
|
253
253
|
"control.sessionOptions": unavailableCapability("effect-scope-not-productionized", "Model and thinking APIs exist but lifecycle and persistence scope enforcement is not enabled"),
|
|
254
254
|
attachments: unavailableCapability("attachments-not-implemented", "Attachments are not implemented"),
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ManagedFailure = { role: "main" | "research" | "review"; code: string; kind: string; requestId: string | null; local: boolean };
|
|
2
|
+
export function failureKind(code: string): string;
|
|
3
|
+
export function failureIsLocal(code: string): boolean;
|
|
4
|
+
export function failureCode(raw: unknown): string;
|
|
5
|
+
export function describeFailure(role: string, raw: unknown): string;
|
|
6
|
+
export function parseFailure(raw: unknown): ManagedFailure | null;
|
|
7
|
+
export function failureIsAdmissionRefusal(code: string): boolean;
|
|
8
|
+
export function failureReason(code: string): string;
|
|
9
|
+
export function unnamedFailureText(raw: unknown): string | null;
|
|
10
|
+
export function failureIsBriefRefusal(code: string): boolean;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// Why a company request failed, as one stable code and one sentence a member
|
|
2
|
+
// can act on. Studio answers with a machine code; the broker and this runtime
|
|
3
|
+
// have their own. The Terminal shows the sentence, the WebUI maps the code to
|
|
4
|
+
// its own copy, and nothing here names the harness model.
|
|
5
|
+
//
|
|
6
|
+
// Kinds: refused (the model's provider declined the request under its usage
|
|
7
|
+
// policy), quota (this key's token budget), provider-limit (the company's model
|
|
8
|
+
// account), account (that account must sign in again), busy (retry shortly),
|
|
9
|
+
// trial-limit (Studio's live-test ceiling), key (import the key again),
|
|
10
|
+
// policy (model or thinking not allowed),
|
|
11
|
+
// new-session (this conversation cannot continue), rejected (the request
|
|
12
|
+
// itself), unreachable (no connection to Studio), service (Studio answered
|
|
13
|
+
// but its model gateway failed), config-changed (key or harness changed while
|
|
14
|
+
// running), update (this Piagent is too old for the harness), tool (a tool's
|
|
15
|
+
// own failure), failed (anything else).
|
|
16
|
+
import { redactSensitiveText } from '../security/sensitive-data.js';
|
|
17
|
+
|
|
18
|
+
const CODES = {
|
|
19
|
+
token_quota_exhausted: ['quota', "this key's token quota is used up"],
|
|
20
|
+
upstream_rate_limited: ['provider-limit', "the company's model account is rate limited or out of usage at the provider"],
|
|
21
|
+
account_capacity_unavailable: ['provider-limit', 'no company model account has capacity for this model right now'],
|
|
22
|
+
concurrency_limit: ['busy', 'this key already runs its maximum number of requests'],
|
|
23
|
+
request_rate_limit: ['busy', 'this key sends requests too fast'],
|
|
24
|
+
helper_capacity_exhausted: ['busy', 'this key already runs its maximum number of subagents'],
|
|
25
|
+
connector_busy: ['busy', "Studio's model gateway is at capacity"],
|
|
26
|
+
operation_unavailable: ['busy', 'Studio could not check this request in time'],
|
|
27
|
+
account_recovering: ['busy', 'the company model account is being checked after an earlier failure'],
|
|
28
|
+
session_account_busy_retry_later: ['busy', "this conversation's model account is busy"],
|
|
29
|
+
session_account_not_ready_retry_later: ['busy', "this conversation's model account is not ready yet"],
|
|
30
|
+
live_trial_limit_reached: ['trial-limit', "Studio's live-test request ceiling is used up; an administrator has to raise it"],
|
|
31
|
+
authentication_required: ['key', 'the company key is no longer valid'],
|
|
32
|
+
invalid_run_grant: ['key', 'the company key or this run is no longer valid'],
|
|
33
|
+
run_grant_required: ['key', 'this key only works through Agent Watch'],
|
|
34
|
+
policy_denied: ['policy', 'this key may not use this model, thinking level or output size'],
|
|
35
|
+
thinking_level_unavailable: ['policy', 'this thinking level is not available for the harness'],
|
|
36
|
+
harness_model_unsupported: ['policy', 'the harness uses a model Studio cannot run'],
|
|
37
|
+
harness_profile_unavailable: ['policy', 'the team has no usable harness'],
|
|
38
|
+
execution_capability_unavailable: ['policy', 'no company model account can run this model'],
|
|
39
|
+
session_account_unavailable_start_new_session: ['new-session', "this conversation's model account is gone"],
|
|
40
|
+
run_state_conflict: ['config-changed', 'this run was closed or replaced'],
|
|
41
|
+
run_recovery_pending_usage: ['busy', "an earlier request of this run is still being accounted"],
|
|
42
|
+
invalid_request: ['rejected', 'Studio rejected the request as invalid'],
|
|
43
|
+
upstream_request_rejected: ['rejected', 'the model rejected the request (too long or not valid for it)'],
|
|
44
|
+
upstream_policy_refusal: ['refused', "the model's provider declined this request under its usage policy"],
|
|
45
|
+
request_too_large: ['rejected', 'the conversation is larger than Studio accepts in one request (4 MiB)'],
|
|
46
|
+
execution_precondition_failed: ['service', 'the chosen company model account changed before the request started'],
|
|
47
|
+
connector_unavailable: ['service', "Studio's model gateway is not available"],
|
|
48
|
+
connector_execution_failed: ['service', "Studio's model gateway failed"],
|
|
49
|
+
inference_unavailable: ['service', 'Studio could not run the request'],
|
|
50
|
+
managed_service_unavailable: ['service', 'Studio could not start the run'],
|
|
51
|
+
upstream_interrupted: ['service', 'the model response was interrupted'],
|
|
52
|
+
upstream_incomplete: ['service', 'the model returned no response'],
|
|
53
|
+
upstream_timeout: ['service', 'the model did not answer in time'],
|
|
54
|
+
upstream_unavailable: ['service', 'the model provider is not answering'],
|
|
55
|
+
upstream_auth_required: ['account', "the company's model account has to sign in again in Studio"],
|
|
56
|
+
'studio-unreachable': ['unreachable', 'Studio could not be reached'],
|
|
57
|
+
'managed-broker:offline': ['unreachable', 'Agent Watch could not reach Studio'],
|
|
58
|
+
'managed-broker-timeout': ['unreachable', 'Agent Watch did not answer in time'],
|
|
59
|
+
'managed-broker-disconnected': ['unreachable', 'the Agent Watch helper stopped'],
|
|
60
|
+
'managed-broker:serverUnavailable': ['service', 'Studio could not start the run'],
|
|
61
|
+
'managed-broker:upstreamUnavailable': ['service', 'Studio could not start the run'],
|
|
62
|
+
'managed-broker:invalidKey': ['key', 'the company key is no longer valid'],
|
|
63
|
+
'managed-broker:permissionDenied': ['key', 'Agent Watch no longer holds a usable company key'],
|
|
64
|
+
'managed-broker:keychainApprovalRequired': ['key', 'macOS has not allowed Agent Watch to read the company key'],
|
|
65
|
+
'managed-broker:quotaExceeded': ['quota', "this key's token quota is used up"],
|
|
66
|
+
'managed-broker:rateLimited': ['busy', 'Studio is limiting this key right now'],
|
|
67
|
+
'managed-broker:identityChanged': ['config-changed', 'the key, harness or run changed in Studio'],
|
|
68
|
+
'managed-broker:invalidResponse': ['config-changed', 'Studio refused to start the run with the current harness'],
|
|
69
|
+
'managed-route-changed': ['config-changed', 'the harness model changed while this run was active'],
|
|
70
|
+
'managed-grant-invalid': ['config-changed', 'the harness changed and this conversation still has the old one'],
|
|
71
|
+
'managed-configuration-unavailable': ['config-changed', 'the key has no usable harness'],
|
|
72
|
+
'managed-main-model-unavailable': ['policy', 'the harness main model is not available to this key'],
|
|
73
|
+
'managed-native-catalog-update-required': ['update', 'this Piagent does not know the harness model; update Piagent'],
|
|
74
|
+
'managed-session-scope-changed': ['new-session', 'this conversation belongs to another member or another Studio'],
|
|
75
|
+
'managed-run-required': ['config-changed', 'no company run is active'],
|
|
76
|
+
'managed-helper-unavailable': ['busy', 'this subagent is already running'],
|
|
77
|
+
'managed-helper-used': ['tool', 'this subagent already ran for this message; each runs once per message'],
|
|
78
|
+
'managed-helper-limit': ['tool', 'this subagent already ran 8 times for this message'],
|
|
79
|
+
helper_run_limit_reached: ['tool', 'this subagent already ran 8 times for this message'],
|
|
80
|
+
'managed-helper-cancelled': ['tool', 'the subagent was stopped'],
|
|
81
|
+
'managed-helper-failed': ['failed', 'the subagent ended without an answer'],
|
|
82
|
+
'web-search-empty': ['tool', 'the search returned nothing'],
|
|
83
|
+
'managed-web-search-unavailable': ['tool', 'web search runs only on a Claude or Codex model; use web_fetch with a known address'],
|
|
84
|
+
'managed-session-open-elsewhere': ['new-session', 'this conversation is running in the Piagent WebUI; continue it there or start a new one'],
|
|
85
|
+
'managed-session-lease-unavailable': ['failed', 'Piagent could not record which process runs this conversation'],
|
|
86
|
+
'managed-plan-required': ['tool', 'for a complex task the Harness asks for a checklist (update_plan) before the first file edit'],
|
|
87
|
+
'managed-plan-invalid': ['tool', 'the checklist was not in the expected form'],
|
|
88
|
+
'managed-request-failed': ['failed', 'the request could not be sent'],
|
|
89
|
+
'studio-request-failed': ['failed', 'Studio answered with an error'],
|
|
90
|
+
};
|
|
91
|
+
const ROLES = { main: 'main agent', research: 'research subagent', review: 'review subagent' };
|
|
92
|
+
const REQUEST = /\b([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\b/;
|
|
93
|
+
const NETWORK = /fetch failed|econnrefused|econnreset|enotfound|ehostunreach|etimedout|socket hang up|network|connection error|terminated|timed? ?out|aborted due to timeout/i;
|
|
94
|
+
|
|
95
|
+
export function failureKind(code) { return CODES[code]?.[0] ?? 'failed'; }
|
|
96
|
+
// Studio said no at admission, before any model account was asked: the same
|
|
97
|
+
// request can be asked again later, and nothing reaches the model twice. A
|
|
98
|
+
// failure after admission (the model's own error, an interrupted answer) is
|
|
99
|
+
// never in this list: replaying it could repeat billed work.
|
|
100
|
+
const ADMISSION_REFUSALS = new Set(['session_account_busy_retry_later', 'session_account_not_ready_retry_later',
|
|
101
|
+
'concurrency_limit', 'account_capacity_unavailable', 'request_rate_limit']);
|
|
102
|
+
export function failureIsAdmissionRefusal(code) { return ADMISSION_REFUSALS.has(code); }
|
|
103
|
+
// Refused before the model did any work, for a reason that passes in seconds:
|
|
104
|
+
// the account's sign-in was being renewed (the provider refused the old one)
|
|
105
|
+
// or changed just before the request started, or the run's two-minute grant
|
|
106
|
+
// lapsed under the request (after the Mac slept, Studio's clock catches up
|
|
107
|
+
// at once), or Studio could not check the request's grant in its 5 s (many
|
|
108
|
+
// requests of one member at once); each ask renews the grant first. Asked
|
|
109
|
+
// again a few times only: an account that really has to sign in again, or a
|
|
110
|
+
// run that was closed, still says so.
|
|
111
|
+
const BRIEF_REFUSALS = new Set(['execution_precondition_failed', 'upstream_auth_required', 'invalid_run_grant', 'operation_unavailable']);
|
|
112
|
+
export function failureIsBriefRefusal(code) { return BRIEF_REFUSALS.has(code); }
|
|
113
|
+
// The words of a failure no code names, redacted, for a record nobody is
|
|
114
|
+
// shown; null for a named failure.
|
|
115
|
+
export function unnamedFailureText(raw) {
|
|
116
|
+
return ['managed-request-failed', 'studio-request-failed'].includes(failureCode(raw)) ? redactSensitiveText(String(raw ?? '')).text.slice(0, 600) : null;
|
|
117
|
+
}
|
|
118
|
+
// The sentence alone, for a status line that already says who is waiting.
|
|
119
|
+
export function failureReason(code) { return (CODES[code] ?? CODES['managed-request-failed'])[1]; }
|
|
120
|
+
// A failure Studio never saw: nothing was sent, so it is not a model request.
|
|
121
|
+
export function failureIsLocal(code) { return code.startsWith('managed-') || code === 'studio-unreachable'; }
|
|
122
|
+
|
|
123
|
+
function known(code) { return typeof code === 'string' && Object.hasOwn(CODES, code) ? code : null; }
|
|
124
|
+
|
|
125
|
+
// The stable code inside any error text this runtime can produce or receive.
|
|
126
|
+
export function failureCode(raw) {
|
|
127
|
+
const text = String(raw ?? '');
|
|
128
|
+
const tagged = /\[([A-Za-z0-9_:.-]{1,80})\](?: \(request [0-9a-f-]{36}\))?\s*$/.exec(text)?.[1];
|
|
129
|
+
if (known(tagged)) return tagged;
|
|
130
|
+
const studio = /"code"\s*:\s*"([a-z0-9_]{1,80})"/.exec(text)?.[1] ?? /web-search-failed: ([a-z0-9_-]{1,80})/.exec(text)?.[1];
|
|
131
|
+
if (studio === 'initial_request_limit_reached') return 'live_trial_limit_reached';
|
|
132
|
+
if (known(studio)) return studio;
|
|
133
|
+
const local = /(managed-broker:[A-Za-z_]{1,80}|managed-[a-z-]{1,80}|web-search-empty)/.exec(text)?.[1];
|
|
134
|
+
if (known(local)) return local;
|
|
135
|
+
if (studio || /\bAPI error \(\d{3}\)|^\d{3} \{/.test(text)) return 'studio-request-failed';
|
|
136
|
+
// The provider declined the request itself (its usage policy), not Studio:
|
|
137
|
+
// the same request is declined again, so it is said in words.
|
|
138
|
+
if (/usage policy|violative|refusals-and-fallback/i.test(text)) return 'upstream_policy_refusal';
|
|
139
|
+
// The provider library's words for an answer stream that ended empty: Studio
|
|
140
|
+
// admitted the request and nothing came back. It is not asked again.
|
|
141
|
+
if (/stream ended without a (terminal event|stop reason)|response has no body/i.test(text)) return 'upstream_incomplete';
|
|
142
|
+
if (NETWORK.test(text)) return 'studio-unreachable';
|
|
143
|
+
return 'managed-request-failed';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// "Agent Watch main agent: <what failed> [code] (request <id>)". The role says
|
|
147
|
+
// who failed; the model behind it is the harness's business, not the reader's.
|
|
148
|
+
export function describeFailure(role, raw) {
|
|
149
|
+
const code = failureCode(raw), request = REQUEST.exec(String(raw ?? ''))?.[1];
|
|
150
|
+
return `Agent Watch ${ROLES[role] ?? ROLES.main}: ${CODES[code][1]} [${code}]${request && !failureIsLocal(code) ? ` (request ${request})` : ''}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// What a reader of a stored error message needs: who, what kind, which code
|
|
154
|
+
// and Studio's request id when there is one. Null when it is not a company
|
|
155
|
+
// failure this module recognises the shape of.
|
|
156
|
+
export function parseFailure(raw) {
|
|
157
|
+
const text = String(raw ?? '');
|
|
158
|
+
if (!text.trim()) return null;
|
|
159
|
+
const code = failureCode(text);
|
|
160
|
+
const role = /^Agent Watch (main agent|research subagent|review subagent): /.exec(text)?.[1]?.split(' ')[0]
|
|
161
|
+
?? /(research|review) subagent/.exec(text)?.[1] ?? 'main';
|
|
162
|
+
const request = failureIsLocal(code) ? null : REQUEST.exec(text)?.[1] ?? null;
|
|
163
|
+
return { role, code, kind: failureKind(code), requestId: request, local: failureIsLocal(code) };
|
|
164
|
+
}
|
|
@@ -4,8 +4,11 @@ import {
|
|
|
4
4
|
authenticatedCatalogDigest,
|
|
5
5
|
CAPABILITY_BANDS,
|
|
6
6
|
createModelRouteDecision,
|
|
7
|
+
LEGACY_OPENAI_CODEX_MODEL_ROUTE_MAPPING_VERSION,
|
|
8
|
+
OPENAI_CODEX_MODEL_ROUTE_MAPPING_VERSION,
|
|
7
9
|
type CapabilityBand,
|
|
8
10
|
type ModelRouteDecision,
|
|
11
|
+
type ModelRouteMappingVersion,
|
|
9
12
|
type ModelSelectionSource,
|
|
10
13
|
type ParentRoutingMode,
|
|
11
14
|
type RoutingObjective
|
|
@@ -22,33 +25,47 @@ export type ModelRoutePolicyInput = {
|
|
|
22
25
|
current: { provider: string | null; modelId: string | null; effort: string | null };
|
|
23
26
|
freshTaskBoundary: boolean;
|
|
24
27
|
hostBoundary: ModelRouteHostBoundary;
|
|
28
|
+
mappingVersion?: ModelRouteMappingVersion;
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
type Candidate = { provider: "openai-codex"; modelId: string; effort: string };
|
|
28
32
|
|
|
29
|
-
const
|
|
33
|
+
const LEGACY_BAND_CANDIDATES: Record<CapabilityBand, Candidate[]> = {
|
|
30
34
|
low: [{ provider: "openai-codex", modelId: "gpt-5.6-luna", effort: "medium" }],
|
|
31
35
|
medium: [{ provider: "openai-codex", modelId: "gpt-5.6-terra", effort: "medium" }],
|
|
32
36
|
high: [{ provider: "openai-codex", modelId: "gpt-5.6-sol", effort: "high" }],
|
|
33
37
|
ultra: [{ provider: "openai-codex", modelId: "gpt-5.6-sol", effort: "xhigh" }]
|
|
34
38
|
};
|
|
39
|
+
const BAND_CANDIDATES: Record<CapabilityBand, Candidate[]> = {
|
|
40
|
+
low: [{ provider: "openai-codex", modelId: "gpt-6-luna", effort: "medium" }, ...LEGACY_BAND_CANDIDATES.low],
|
|
41
|
+
medium: [{ provider: "openai-codex", modelId: "gpt-6-sol", effort: "medium" }, ...LEGACY_BAND_CANDIDATES.medium],
|
|
42
|
+
high: [{ provider: "openai-codex", modelId: "gpt-6-sol", effort: "high" }, ...LEGACY_BAND_CANDIDATES.high],
|
|
43
|
+
ultra: [{ provider: "openai-codex", modelId: "gpt-6-astra", effort: "xhigh" }, ...LEGACY_BAND_CANDIDATES.ultra]
|
|
44
|
+
};
|
|
35
45
|
const BAND_INDEX = new Map(CAPABILITY_BANDS.map((band, index) => [band, index]));
|
|
36
46
|
|
|
37
|
-
function exactCandidate(catalog: AuthenticatedModelCatalog, band: CapabilityBand): { model: AuthenticatedModelCatalogEntry; effort: string } | undefined {
|
|
38
|
-
|
|
47
|
+
function exactCandidate(catalog: AuthenticatedModelCatalog, band: CapabilityBand, mappingVersion: ModelRouteMappingVersion): { model: AuthenticatedModelCatalogEntry; effort: string; legacyFallback: boolean } | undefined {
|
|
48
|
+
const candidates = mappingVersion === LEGACY_OPENAI_CODEX_MODEL_ROUTE_MAPPING_VERSION ? LEGACY_BAND_CANDIDATES[band] : BAND_CANDIDATES[band];
|
|
49
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
39
50
|
const model = catalog.models.find((entry) => entry.provider === candidate.provider
|
|
40
51
|
&& entry.modelId === candidate.modelId
|
|
41
52
|
&& (entry.supportedThinkingLevels === null || entry.supportedThinkingLevels.includes(candidate.effort)));
|
|
42
|
-
if (model) return { model, effort: candidate.effort };
|
|
53
|
+
if (model) return { model, effort: candidate.effort, legacyFallback: mappingVersion === OPENAI_CODEX_MODEL_ROUTE_MAPPING_VERSION && index > 0 };
|
|
43
54
|
}
|
|
44
55
|
return undefined;
|
|
45
56
|
}
|
|
46
57
|
|
|
47
|
-
function currentBand(modelId: string | null): CapabilityBand | undefined {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
function currentBand(modelId: string | null, effort: string | null): CapabilityBand | undefined {
|
|
59
|
+
if (modelId === "gpt-6-luna" || modelId === "gpt-5.6-luna") return "low";
|
|
60
|
+
if (modelId === "gpt-6-astra") return "ultra";
|
|
61
|
+
if (modelId === "gpt-6-sol") {
|
|
62
|
+
if (effort === "medium") return "medium";
|
|
63
|
+
if (effort === "xhigh" || effort === "max") return "ultra";
|
|
64
|
+
return "high";
|
|
65
|
+
}
|
|
66
|
+
if (modelId === "gpt-5.6-terra") return "medium";
|
|
67
|
+
if (modelId === "gpt-5.6-sol") return "high";
|
|
68
|
+
return undefined;
|
|
52
69
|
}
|
|
53
70
|
|
|
54
71
|
function maxBand(left: CapabilityBand, right: CapabilityBand): CapabilityBand {
|
|
@@ -102,6 +119,7 @@ function downgradeSteps(current: CapabilityBand | undefined, target: CapabilityB
|
|
|
102
119
|
}
|
|
103
120
|
|
|
104
121
|
export function routeParentModel(input: ModelRoutePolicyInput): ModelRouteDecision {
|
|
122
|
+
const mappingVersion = input.mappingVersion ?? OPENAI_CODEX_MODEL_ROUTE_MAPPING_VERSION;
|
|
105
123
|
const floor = safetyFloor(input.features);
|
|
106
124
|
const desired = objectiveBand(floor.band, input.objective);
|
|
107
125
|
const catalogDigest = authenticatedCatalogDigest(input.catalog);
|
|
@@ -114,7 +132,8 @@ export function routeParentModel(input: ModelRoutePolicyInput): ModelRouteDecisi
|
|
|
114
132
|
currentModelId: input.current.modelId,
|
|
115
133
|
currentEffort: input.current.effort,
|
|
116
134
|
selectionSource: input.selectionSource,
|
|
117
|
-
catalogDigest
|
|
135
|
+
catalogDigest,
|
|
136
|
+
mappingVersion
|
|
118
137
|
} as const;
|
|
119
138
|
if (input.mode === "off") return createModelRouteDecision({
|
|
120
139
|
...common, capabilityBand: "abstain", fallbackBand: desired, provider: input.current.provider,
|
|
@@ -136,14 +155,15 @@ export function routeParentModel(input: ModelRoutePolicyInput): ModelRouteDecisi
|
|
|
136
155
|
confidence: "low", disposition: "unavailable", reasonCodes: ["authenticated-catalog-unavailable", ...floor.reasons],
|
|
137
156
|
downgradeSteps: 0, enforced: false
|
|
138
157
|
});
|
|
139
|
-
const candidate = exactCandidate(input.catalog, desired);
|
|
158
|
+
const candidate = exactCandidate(input.catalog, desired, mappingVersion);
|
|
140
159
|
if (!candidate) return createModelRouteDecision({
|
|
141
160
|
...common, capabilityBand: "abstain", fallbackBand: desired, provider: null, modelId: null, effort: null,
|
|
142
161
|
confidence: "low", disposition: "unavailable", reasonCodes: ["preferred-model-or-effort-unavailable", "no-silent-substitution", ...floor.reasons],
|
|
143
162
|
downgradeSteps: 0, enforced: false
|
|
144
163
|
});
|
|
145
|
-
const reasons = [...floor.reasons, `objective-${input.objective}`, `candidate-${desired}`, "runtime-catalog-match"
|
|
146
|
-
|
|
164
|
+
const reasons = [...floor.reasons, `objective-${input.objective}`, `candidate-${desired}`, "runtime-catalog-match",
|
|
165
|
+
...(candidate.legacyFallback ? ["legacy-model-fallback"] : [])];
|
|
166
|
+
const steps = downgradeSteps(currentBand(input.current.modelId, input.current.effort), desired);
|
|
147
167
|
if (input.mode === "shadow") return createModelRouteDecision({
|
|
148
168
|
...common, capabilityBand: desired, fallbackBand: null, provider: candidate.model.provider, modelId: candidate.model.modelId,
|
|
149
169
|
effort: candidate.effort, confidence: floor.band === "low" || floor.band === "ultra" ? "high" : "medium",
|