@linimin/pi-letscook 0.1.53 → 0.1.55
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 +12 -0
- package/README.md +17 -38
- package/extensions/completion/driver.ts +44 -110
- package/extensions/completion/index.ts +10 -74
- package/extensions/completion/prompt-surfaces.ts +53 -380
- package/extensions/completion/proposal.ts +5 -65
- package/extensions/completion/role-runner.ts +4 -311
- package/extensions/completion/state-store.ts +212 -5
- package/extensions/completion/transcription.ts +0 -8
- package/extensions/completion/types.ts +0 -114
- package/package.json +15 -4
- package/scripts/active-slice-contract-test.sh +61 -6
- package/scripts/context-proposal-test.sh +33 -29
- package/scripts/legacy-cleanup-test.sh +11 -0
- package/scripts/refocus-test.sh +10 -11
- package/scripts/release-check.sh +21 -29
- package/scripts/role-runner-contract-test.sh +1 -2
- package/scripts/rubric-contract-test.sh +0 -1
- package/scripts/smoke-test.sh +14 -10
- package/extensions/completion/input-routing.ts +0 -819
- package/scripts/cook-trigger-routing-test.sh +0 -1122
|
@@ -1,1122 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
-
pi() {
|
|
6
|
-
env -u PI_COMPLETION_ROLE command pi --no-extensions "$@"
|
|
7
|
-
}
|
|
8
|
-
TMPDIR="$(mktemp -d)"
|
|
9
|
-
trap 'rm -rf "$TMPDIR"' EXIT
|
|
10
|
-
|
|
11
|
-
export PI_COMPLETION_TEST_TRIGGER_MODE=router
|
|
12
|
-
|
|
13
|
-
write_session() {
|
|
14
|
-
local session_path="$1"
|
|
15
|
-
local cwd="$2"
|
|
16
|
-
local text="$3"
|
|
17
|
-
python3 - "$session_path" "$cwd" "$text" <<'PY'
|
|
18
|
-
import json
|
|
19
|
-
import sys
|
|
20
|
-
from pathlib import Path
|
|
21
|
-
|
|
22
|
-
session_path = Path(sys.argv[1])
|
|
23
|
-
cwd = sys.argv[2]
|
|
24
|
-
text = sys.argv[3]
|
|
25
|
-
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
26
|
-
entries = [
|
|
27
|
-
{
|
|
28
|
-
"type": "session",
|
|
29
|
-
"version": 3,
|
|
30
|
-
"id": "11111111-1111-4111-8111-111111111111",
|
|
31
|
-
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
32
|
-
"cwd": cwd,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
"type": "message",
|
|
36
|
-
"id": "a1b2c3d4",
|
|
37
|
-
"parentId": None,
|
|
38
|
-
"timestamp": "2026-01-01T00:00:01.000Z",
|
|
39
|
-
"message": {
|
|
40
|
-
"role": "user",
|
|
41
|
-
"content": text,
|
|
42
|
-
"timestamp": 1767225601000,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
]
|
|
46
|
-
with session_path.open('w', encoding='utf-8') as fh:
|
|
47
|
-
for entry in entries:
|
|
48
|
-
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
49
|
-
PY
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
write_mixed_session() {
|
|
53
|
-
local session_path="$1"
|
|
54
|
-
local cwd="$2"
|
|
55
|
-
local assistant_text="$3"
|
|
56
|
-
local user_text="$4"
|
|
57
|
-
python3 - "$session_path" "$cwd" "$assistant_text" "$user_text" <<'PY'
|
|
58
|
-
import json
|
|
59
|
-
import sys
|
|
60
|
-
from pathlib import Path
|
|
61
|
-
|
|
62
|
-
session_path = Path(sys.argv[1])
|
|
63
|
-
cwd = sys.argv[2]
|
|
64
|
-
assistant_text = sys.argv[3]
|
|
65
|
-
user_text = sys.argv[4]
|
|
66
|
-
session_path.parent.mkdir(parents=True, exist_ok=True)
|
|
67
|
-
entries = [
|
|
68
|
-
{
|
|
69
|
-
"type": "session",
|
|
70
|
-
"version": 3,
|
|
71
|
-
"id": "11111111-1111-4111-8111-111111111111",
|
|
72
|
-
"timestamp": "2026-01-01T00:00:00.000Z",
|
|
73
|
-
"cwd": cwd,
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
"type": "message",
|
|
77
|
-
"id": "a1b2c3d4",
|
|
78
|
-
"parentId": None,
|
|
79
|
-
"timestamp": "2026-01-01T00:00:01.000Z",
|
|
80
|
-
"message": {
|
|
81
|
-
"role": "assistant",
|
|
82
|
-
"content": assistant_text,
|
|
83
|
-
"timestamp": 1767225601000,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
"type": "message",
|
|
88
|
-
"id": "b2c3d4e5",
|
|
89
|
-
"parentId": "a1b2c3d4",
|
|
90
|
-
"timestamp": "2026-01-01T00:00:02.000Z",
|
|
91
|
-
"message": {
|
|
92
|
-
"role": "user",
|
|
93
|
-
"content": user_text,
|
|
94
|
-
"timestamp": 1767225602000,
|
|
95
|
-
},
|
|
96
|
-
},
|
|
97
|
-
]
|
|
98
|
-
with session_path.open('w', encoding='utf-8') as fh:
|
|
99
|
-
for entry in entries:
|
|
100
|
-
fh.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
|
101
|
-
PY
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
write_completion_state() {
|
|
105
|
-
local root="$1"
|
|
106
|
-
local mission="$2"
|
|
107
|
-
local continuation_policy="$3"
|
|
108
|
-
local project_done="$4"
|
|
109
|
-
local current_phase="$5"
|
|
110
|
-
local next_role="$6"
|
|
111
|
-
local next_action="$7"
|
|
112
|
-
python3 - "$root" "$mission" "$continuation_policy" "$project_done" "$current_phase" "$next_role" "$next_action" <<'PY'
|
|
113
|
-
import json
|
|
114
|
-
import sys
|
|
115
|
-
from pathlib import Path
|
|
116
|
-
|
|
117
|
-
root = Path(sys.argv[1])
|
|
118
|
-
mission = sys.argv[2]
|
|
119
|
-
continuation_policy = sys.argv[3]
|
|
120
|
-
project_done = sys.argv[4].lower() == 'true'
|
|
121
|
-
current_phase = sys.argv[5]
|
|
122
|
-
next_role = None if sys.argv[6] == 'null' else sys.argv[6]
|
|
123
|
-
next_action = None if sys.argv[7] == 'null' else sys.argv[7]
|
|
124
|
-
agent = root / '.agent'
|
|
125
|
-
agent.mkdir(parents=True, exist_ok=True)
|
|
126
|
-
(agent / 'mission.md').write_text(
|
|
127
|
-
'# Mission\n\n'
|
|
128
|
-
f'Project: {root.name}\n\n'
|
|
129
|
-
'Mission anchor:\n'
|
|
130
|
-
f'{mission}\n\n'
|
|
131
|
-
"This file is a tracked human-readable statement of the repo's completion mission. Re-grounders may refine this file when repo truth becomes clearer, but it must stay truthful to shipped behavior and the active completion objective.\n",
|
|
132
|
-
encoding='utf-8',
|
|
133
|
-
)
|
|
134
|
-
profile = {
|
|
135
|
-
'schema_version': 1,
|
|
136
|
-
'protocol_id': 'completion',
|
|
137
|
-
'project_name': root.name,
|
|
138
|
-
'required_stop_judges': 3,
|
|
139
|
-
'priority_policy_id': 'completion-default',
|
|
140
|
-
'task_type': 'completion-workflow',
|
|
141
|
-
'evaluation_profile': 'completion-rubric-v1',
|
|
142
|
-
'docs_surfaces': ['README.md', 'CHANGELOG.md'],
|
|
143
|
-
}
|
|
144
|
-
state = {
|
|
145
|
-
'schema_version': 1,
|
|
146
|
-
'mission_anchor': mission,
|
|
147
|
-
'current_phase': current_phase,
|
|
148
|
-
'continuation_policy': continuation_policy,
|
|
149
|
-
'continuation_reason': 'test fixture',
|
|
150
|
-
'project_done': project_done,
|
|
151
|
-
'task_type': 'completion-workflow',
|
|
152
|
-
'evaluation_profile': 'completion-rubric-v1',
|
|
153
|
-
'requires_reground': continuation_policy == 'done',
|
|
154
|
-
'slices_since_last_reground': 0,
|
|
155
|
-
'remaining_release_blockers': 0,
|
|
156
|
-
'remaining_high_value_gaps': 0,
|
|
157
|
-
'unsatisfied_contract_ids': [],
|
|
158
|
-
'release_blocker_ids': [],
|
|
159
|
-
'next_mandatory_action': next_action,
|
|
160
|
-
'next_mandatory_role': next_role,
|
|
161
|
-
'remaining_stop_judges': 3,
|
|
162
|
-
'last_reground_at': '2026-01-01T00:00:00Z',
|
|
163
|
-
'last_auditor_verdict': None,
|
|
164
|
-
'contract_status': 'partial' if continuation_policy != 'done' else 'done',
|
|
165
|
-
'latest_completed_slice': None,
|
|
166
|
-
'latest_verified_slice': None,
|
|
167
|
-
}
|
|
168
|
-
plan = {
|
|
169
|
-
'schema_version': 1,
|
|
170
|
-
'mission_anchor': mission,
|
|
171
|
-
'task_type': 'completion-workflow',
|
|
172
|
-
'evaluation_profile': 'completion-rubric-v1',
|
|
173
|
-
'last_reground_at': '2026-01-01T00:00:00Z',
|
|
174
|
-
'plan_basis': 'test-fixture',
|
|
175
|
-
'candidate_slices': [],
|
|
176
|
-
}
|
|
177
|
-
active = {
|
|
178
|
-
'schema_version': 1,
|
|
179
|
-
'mission_anchor': mission,
|
|
180
|
-
'task_type': 'completion-workflow',
|
|
181
|
-
'evaluation_profile': 'completion-rubric-v1',
|
|
182
|
-
'status': 'idle',
|
|
183
|
-
'slice_id': None,
|
|
184
|
-
'goal': None,
|
|
185
|
-
'contract_ids': [],
|
|
186
|
-
'acceptance_criteria': [],
|
|
187
|
-
'priority': None,
|
|
188
|
-
'why_now': None,
|
|
189
|
-
'blocked_on': [],
|
|
190
|
-
'locked_notes': [],
|
|
191
|
-
'must_fix_findings': [],
|
|
192
|
-
'implementation_surfaces': [],
|
|
193
|
-
'verification_commands': [],
|
|
194
|
-
'basis_commit': None,
|
|
195
|
-
'remaining_contract_ids_before': [],
|
|
196
|
-
'release_blocker_count_before': None,
|
|
197
|
-
'high_value_gap_count_before': None,
|
|
198
|
-
}
|
|
199
|
-
verification = {
|
|
200
|
-
'schema_version': 1,
|
|
201
|
-
'artifact_type': 'completion-verification-evidence',
|
|
202
|
-
'subject_type': 'fixture',
|
|
203
|
-
'slice_id': None,
|
|
204
|
-
'goal': None,
|
|
205
|
-
'contract_ids': [],
|
|
206
|
-
'basis_commit': None,
|
|
207
|
-
'head_sha': None,
|
|
208
|
-
'verification_commands': [],
|
|
209
|
-
'outcome': 'not_recorded',
|
|
210
|
-
'recorded_at': None,
|
|
211
|
-
'summary': 'test fixture',
|
|
212
|
-
}
|
|
213
|
-
(agent / 'profile.json').write_text(json.dumps(profile, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
214
|
-
(agent / 'state.json').write_text(json.dumps(state, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
215
|
-
(agent / 'plan.json').write_text(json.dumps(plan, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
216
|
-
(agent / 'active-slice.json').write_text(json.dumps(active, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
217
|
-
(agent / 'verification-evidence.json').write_text(json.dumps(verification, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
|
|
218
|
-
PY
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
write_fallback_extension() {
|
|
222
|
-
local target="$1"
|
|
223
|
-
cat >"$target" <<'TS'
|
|
224
|
-
import { promises as fsp } from "node:fs";
|
|
225
|
-
import * as path from "node:path";
|
|
226
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
227
|
-
|
|
228
|
-
export default function (pi: ExtensionAPI) {
|
|
229
|
-
pi.on("input", async (event) => {
|
|
230
|
-
const targetPath = process.env.PI_COOK_TRIGGER_FALLBACK_PATH;
|
|
231
|
-
const allowedSource = process.env.PI_COOK_TRIGGER_FALLBACK_SOURCE ?? "interactive";
|
|
232
|
-
if (!targetPath) return { action: "continue" };
|
|
233
|
-
if (allowedSource !== "any" && event.source !== allowedSource) return { action: "continue" };
|
|
234
|
-
await fsp.mkdir(path.dirname(targetPath), { recursive: true });
|
|
235
|
-
await fsp.writeFile(
|
|
236
|
-
targetPath,
|
|
237
|
-
`${JSON.stringify({ text: event.text, source: event.source ?? null }, null, 2)}\n`,
|
|
238
|
-
"utf8",
|
|
239
|
-
);
|
|
240
|
-
return { action: "handled" };
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
TS
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
write_extension_sender_extension() {
|
|
247
|
-
local target="$1"
|
|
248
|
-
cat >"$target" <<'TS'
|
|
249
|
-
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
250
|
-
|
|
251
|
-
export default function (pi: ExtensionAPI) {
|
|
252
|
-
let sent = false;
|
|
253
|
-
pi.on("input", async (event) => {
|
|
254
|
-
if (sent || event.source !== "interactive" || event.text !== "__seed__") return { action: "continue" };
|
|
255
|
-
sent = true;
|
|
256
|
-
const text = process.env.PI_COOK_TRIGGER_EXTENSION_SOURCE_TEXT?.trim();
|
|
257
|
-
if (text) pi.sendUserMessage(text);
|
|
258
|
-
return { action: "handled" };
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
TS
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
FALLBACK_EXTENSION="$TMPDIR/fallback-handler.ts"
|
|
265
|
-
SENDER_EXTENSION="$TMPDIR/extension-sender.ts"
|
|
266
|
-
write_fallback_extension "$FALLBACK_EXTENSION"
|
|
267
|
-
write_extension_sender_extension "$SENDER_EXTENSION"
|
|
268
|
-
|
|
269
|
-
STARTUP_DISCUSSION=$'Mission: Route natural-language handoff into the shared /cook entry.\nScope:\n- Add an input hook before the primary agent starts implementation work.\n- Keep /cook as the canonical workflow boundary.\nConstraints:\n- Do not transform natural-language input into /cook.\nAcceptance:\n- Route execution handoff text into the shared /cook entry behind approval-only confirmation.'
|
|
270
|
-
STARTUP_MISSION='Route natural-language handoff into the shared /cook entry.'
|
|
271
|
-
ACTIVE_MISSION='Keep the startup-only natural-language /cook handoff working.'
|
|
272
|
-
REFOCUS_DISCUSSION=$'Mission: Expand commandless routing to bias-aware resume and refocus offers.\nScope:\n- Distinguish startup, resume, refocus, and next-round workflow offers before the primary agent runs.\n- Keep confirmed handoffs on the shared /cook entry.\nConstraints:\n- Do not duplicate /cook workflow logic.\nAcceptance:\n- Resume and refocus handoffs reach the shared /cook entry with bias metadata.'
|
|
273
|
-
REFOCUS_MISSION='Expand commandless routing to bias-aware resume and refocus offers.'
|
|
274
|
-
NEXT_ROUND_DISCUSSION=$'Mission: Start the next completion workflow round for docs parity cleanup.\nScope:\n- Refresh docs and tests around commandless workflow entry.\n- Keep the existing workflow history done.\nConstraints:\n- Do not reopen the finished workflow mission.\nAcceptance:\n- The next workflow round uses a new mission anchor without reopening the finished one.'
|
|
275
|
-
NEXT_ROUND_MISSION='Start the next completion workflow round for docs parity cleanup.'
|
|
276
|
-
STARTUP_ROUTER_TEXT='把 login redirect 補完整,順便加測試'
|
|
277
|
-
NORMAL_ROUTER_TEXT='你覺得 login redirect 應該怎麼拆比較好?'
|
|
278
|
-
RESUME_ROUTER_TEXT='接著把剩下的測試補完'
|
|
279
|
-
REFOCUS_ROUTER_TEXT='先不要做 redirect 了,這輪改修 session timeout'
|
|
280
|
-
NEXT_ROUND_ROUTER_TEXT='這輪改做 docs parity cleanup'
|
|
281
|
-
UNCLEAR_ROUTER_TEXT='先做這個吧'
|
|
282
|
-
|
|
283
|
-
STARTUP_CLASSIFIER_OUTPUT='{"decision":"offer_workflow","confidence":0.95,"workflow_bias":"startup","reason":"The latest input is a startup handoff from recent discussion into the canonical workflow.","focusHint":"shared /cook entry handoff","evidence":["current input is a start-execution phrase","recent discussion already defines a concrete workflow mission"],"riskFlags":[]}'
|
|
284
|
-
RESUME_CLASSIFIER_OUTPUT='{"decision":"offer_workflow","confidence":0.94,"workflow_bias":"resume","reason":"The latest input is continuing the active workflow.","focusHint":"resume current workflow","evidence":["canonical state already exists","the latest input is a continue-style handoff"],"riskFlags":[]}'
|
|
285
|
-
REFOCUS_CLASSIFIER_OUTPUT='{"decision":"offer_workflow","confidence":0.91,"workflow_bias":"refocus","reason":"The latest input is starting a different workflow direction from recent discussion.","focusHint":"bias-aware resume and refocus offers","evidence":["recent discussion changes the mission","the latest input confirms starting the new direction"],"riskFlags":["active-workflow-refocus-risk"]}'
|
|
286
|
-
NEXT_ROUND_CLASSIFIER_OUTPUT='{"decision":"offer_workflow","confidence":0.92,"workflow_bias":"next_round","reason":"The previous workflow is done and the latest input starts a new implementation round.","focusHint":"next workflow round docs parity","evidence":["canonical workflow is already done","recent discussion defines a new task"],"riskFlags":[]}'
|
|
287
|
-
NORMAL_CLASSIFIER_OUTPUT='{"decision":"normal_prompt","confidence":0.82,"workflow_bias":"unknown","reason":"The latest input is still asking the main agent to explain instead of handing control to /cook.","evidence":["the user is still asking for explanation in the main chat"],"riskFlags":["possible-normal-agent-request"]}'
|
|
288
|
-
UNCLEAR_CLASSIFIER_OUTPUT='{"decision":"unclear","confidence":0.41,"workflow_bias":"unknown","reason":"The latest input looks workflow-related but the safer path is to clarify whether this should resume or refocus the workflow.","focusHint":"bias-aware resume and refocus offers","evidence":["the latest input is a short start-intent acknowledgement","recent discussion suggests a different workflow candidate"],"riskFlags":["ambiguous-approval","multiple_candidate_missions"]}'
|
|
289
|
-
|
|
290
|
-
# Router-mode startup routing should enter the shared /cook flow with natural-language metadata.
|
|
291
|
-
ROUTE_ROOT="$TMPDIR/route-repo"
|
|
292
|
-
ROUTE_SESSION="$TMPDIR/route-session.jsonl"
|
|
293
|
-
ROUTE_PROMPT="$TMPDIR/route-driver-prompt.txt"
|
|
294
|
-
ROUTE_ROUTING="$TMPDIR/route-routing.json"
|
|
295
|
-
ROUTE_CLASSIFIER="$TMPDIR/route-classifier.json"
|
|
296
|
-
ROUTE_CONFIRMATION="$TMPDIR/route-confirmation.json"
|
|
297
|
-
ROUTE_FALLBACK="$TMPDIR/route-fallback.json"
|
|
298
|
-
mkdir -p "$ROUTE_ROOT"
|
|
299
|
-
cd "$ROUTE_ROOT"
|
|
300
|
-
git init -q
|
|
301
|
-
write_session "$ROUTE_SESSION" "$ROUTE_ROOT" "$STARTUP_DISCUSSION"
|
|
302
|
-
|
|
303
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$ROUTE_FALLBACK" \
|
|
304
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=interactive \
|
|
305
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
306
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
307
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
308
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$ROUTE_PROMPT" \
|
|
309
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
310
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$ROUTE_CLASSIFIER" \
|
|
311
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
312
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH="$ROUTE_CONFIRMATION" \
|
|
313
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$ROUTE_ROUTING" \
|
|
314
|
-
pi --session "$ROUTE_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$STARTUP_ROUTER_TEXT" \
|
|
315
|
-
>"$TMPDIR/pi-cook-trigger-route.out" 2>"$TMPDIR/pi-cook-trigger-route.err"
|
|
316
|
-
|
|
317
|
-
python3 - "$ROUTE_PROMPT" "$ROUTE_ROUTING" "$ROUTE_CLASSIFIER" "$ROUTE_CONFIRMATION" "$ROUTE_FALLBACK" "$STARTUP_MISSION" "$STARTUP_ROUTER_TEXT" "$TMPDIR/pi-cook-trigger-route.out" "$TMPDIR/pi-cook-trigger-route.err" <<'PY'
|
|
318
|
-
import json
|
|
319
|
-
import sys
|
|
320
|
-
from pathlib import Path
|
|
321
|
-
|
|
322
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
323
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
324
|
-
classifier = json.loads(Path(sys.argv[3]).read_text())
|
|
325
|
-
confirmation = json.loads(Path(sys.argv[4]).read_text())
|
|
326
|
-
fallback = Path(sys.argv[5])
|
|
327
|
-
mission = sys.argv[6]
|
|
328
|
-
trigger_text = sys.argv[7]
|
|
329
|
-
output = Path(sys.argv[8]).read_text() + Path(sys.argv[9]).read_text()
|
|
330
|
-
profile = json.loads(Path('.agent/profile.json').read_text())
|
|
331
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
332
|
-
plan = json.loads(Path('.agent/plan.json').read_text())
|
|
333
|
-
active = json.loads(Path('.agent/active-slice.json').read_text())
|
|
334
|
-
|
|
335
|
-
assert routing['action'] == 'routed_to_cook', 'accepted startup handoff should route into the shared /cook entry'
|
|
336
|
-
assert routing['reason'] == 'accepted_takeover', 'accepted startup handoff should record the takeover reason'
|
|
337
|
-
assert routing['classificationDecision'] == 'offer_workflow', 'accepted startup handoff should snapshot the offer_workflow classifier result'
|
|
338
|
-
assert routing['workflowBias'] == 'startup', 'accepted startup handoff should preserve the startup routing bias'
|
|
339
|
-
assert routing['confirmationAction'] == 'start_workflow', 'accepted startup handoff should record the confirmed workflow action'
|
|
340
|
-
assert confirmation['title'] == 'Start a completion workflow from the recent discussion?', 'startup handoff should show the startup-specific workflow offer'
|
|
341
|
-
assert confirmation['actions'][0]['label'] == 'Start workflow', 'startup handoff should show the startup-specific primary action label'
|
|
342
|
-
assert classifier['result']['status'] == 'classified', 'accepted startup handoff should snapshot a classified trigger result'
|
|
343
|
-
assert classifier['result']['classification']['decision'] == 'offer_workflow', 'startup classifier snapshot should preserve offer_workflow'
|
|
344
|
-
assert classifier['result']['classification']['workflowBias'] == 'startup', 'startup classifier snapshot should preserve the startup bias'
|
|
345
|
-
assert not fallback.exists(), 'accepted startup handoff should keep the original interactive input away from later fallback handlers'
|
|
346
|
-
assert 'Start or continue the completion workflow for this repo.' in prompt, 'accepted startup handoff should queue the shared completion driver prompt'
|
|
347
|
-
assert 'Natural-language handoff metadata:' in prompt, 'accepted startup handoff should pass structured handoff metadata into the shared driver prompt'
|
|
348
|
-
assert '- preferred_routing_bias: startup' in prompt, 'accepted startup handoff should preserve the startup routing bias in the shared driver prompt'
|
|
349
|
-
assert f'- trigger_text: {trigger_text}' in prompt, 'accepted startup handoff should preserve the trigger text in the shared driver prompt'
|
|
350
|
-
assert state['mission_anchor'] == mission, 'accepted startup handoff should bootstrap canonical mission state through the shared /cook entry'
|
|
351
|
-
assert plan['mission_anchor'] == mission, 'accepted startup handoff should bootstrap plan.json through the shared /cook entry'
|
|
352
|
-
assert active['mission_anchor'] == mission, 'accepted startup handoff should bootstrap active-slice.json through the shared /cook entry'
|
|
353
|
-
assert profile['task_type'] == 'completion-workflow', 'accepted startup handoff should keep the canonical task_type'
|
|
354
|
-
assert 'Routing natural-language handoff into /cook.' in output, 'accepted startup handoff should notify that /cook took over'
|
|
355
|
-
PY
|
|
356
|
-
|
|
357
|
-
# Send as normal chat should replay the original start-intent message exactly once through the main chat path.
|
|
358
|
-
REPLAY_ROOT="$TMPDIR/replay-repo"
|
|
359
|
-
REPLAY_SESSION="$TMPDIR/replay-session.jsonl"
|
|
360
|
-
REPLAY_PROMPT="$TMPDIR/replay-driver-prompt.txt"
|
|
361
|
-
REPLAY_ROUTING="$TMPDIR/replay-routing.json"
|
|
362
|
-
REPLAY_CLASSIFIER="$TMPDIR/replay-classifier.json"
|
|
363
|
-
REPLAY_CONFIRMATION="$TMPDIR/replay-confirmation.json"
|
|
364
|
-
REPLAY_FALLBACK="$TMPDIR/replay-fallback.json"
|
|
365
|
-
mkdir -p "$REPLAY_ROOT"
|
|
366
|
-
cd "$REPLAY_ROOT"
|
|
367
|
-
git init -q
|
|
368
|
-
write_session "$REPLAY_SESSION" "$REPLAY_ROOT" "$STARTUP_DISCUSSION"
|
|
369
|
-
|
|
370
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$REPLAY_FALLBACK" \
|
|
371
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=any \
|
|
372
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$REPLAY_PROMPT" \
|
|
373
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
374
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$REPLAY_CLASSIFIER" \
|
|
375
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=send_as_normal_chat \
|
|
376
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH="$REPLAY_CONFIRMATION" \
|
|
377
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$REPLAY_ROUTING" \
|
|
378
|
-
pi --session "$REPLAY_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$STARTUP_ROUTER_TEXT" \
|
|
379
|
-
>"$TMPDIR/pi-cook-trigger-replay.out" 2>"$TMPDIR/pi-cook-trigger-replay.err"
|
|
380
|
-
|
|
381
|
-
python3 - "$REPLAY_ROUTING" "$REPLAY_CLASSIFIER" "$REPLAY_CONFIRMATION" "$REPLAY_FALLBACK" "$REPLAY_PROMPT" "$STARTUP_ROUTER_TEXT" "$TMPDIR/pi-cook-trigger-replay.out" "$TMPDIR/pi-cook-trigger-replay.err" <<'PY'
|
|
382
|
-
import json
|
|
383
|
-
import sys
|
|
384
|
-
from pathlib import Path
|
|
385
|
-
|
|
386
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
387
|
-
classifier = json.loads(Path(sys.argv[2]).read_text())
|
|
388
|
-
confirmation = json.loads(Path(sys.argv[3]).read_text())
|
|
389
|
-
fallback = json.loads(Path(sys.argv[4]).read_text())
|
|
390
|
-
driver_prompt = Path(sys.argv[5])
|
|
391
|
-
trigger_text = sys.argv[6]
|
|
392
|
-
output = Path(sys.argv[7]).read_text() + Path(sys.argv[8]).read_text()
|
|
393
|
-
|
|
394
|
-
assert routing['action'] == 'handled', 'send as normal chat should intercept the original workflow offer turn'
|
|
395
|
-
assert routing['reason'] == 'user_sent_as_normal_chat', 'send as normal chat should record the explicit replay decision'
|
|
396
|
-
assert routing['classificationDecision'] == 'offer_workflow', 'send as normal chat should still snapshot the offer_workflow classifier result'
|
|
397
|
-
assert routing['workflowBias'] == 'startup', 'send as normal chat should preserve the original workflow bias'
|
|
398
|
-
assert routing['confirmationAction'] == 'send_as_normal_chat', 'send as normal chat should record the replay confirmation action'
|
|
399
|
-
assert routing['replayedToPrimaryAgent'] is True, 'send as normal chat should record that the original message was replayed to the primary agent'
|
|
400
|
-
assert routing['replayBypassMarkerApplied'] is True, 'send as normal chat should record that the replay used the router-bypass marker'
|
|
401
|
-
assert confirmation['actions'][1]['label'] == 'Send as normal chat', 'workflow offers should expose send as normal chat instead of keep chatting'
|
|
402
|
-
assert classifier['result']['classification']['workflowBias'] == 'startup', 'send as normal chat should preserve the startup bias in the classifier snapshot'
|
|
403
|
-
assert fallback['source'] == 'extension', 'send as normal chat should replay through an extension-originated bypass turn'
|
|
404
|
-
assert fallback['text'] == trigger_text, 'send as normal chat should replay the original prompt text exactly once'
|
|
405
|
-
assert not driver_prompt.exists(), 'send as normal chat should not queue a /cook driver prompt'
|
|
406
|
-
assert not Path('.agent').exists(), 'send as normal chat should not bootstrap canonical workflow state'
|
|
407
|
-
assert 'bypassed router interception' in output, 'send as normal chat should tell the user that the replay bypassed router interception'
|
|
408
|
-
PY
|
|
409
|
-
|
|
410
|
-
# Router-mode normal prompts should still continue to the main agent path after per-turn classification.
|
|
411
|
-
NORMAL_ROOT="$TMPDIR/normal-repo"
|
|
412
|
-
NORMAL_SESSION="$TMPDIR/normal-session.jsonl"
|
|
413
|
-
NORMAL_ROUTING="$TMPDIR/normal-routing.json"
|
|
414
|
-
NORMAL_CLASSIFIER="$TMPDIR/normal-classifier.json"
|
|
415
|
-
NORMAL_FALLBACK="$TMPDIR/normal-fallback.json"
|
|
416
|
-
NORMAL_PROMPT="$TMPDIR/normal-driver-prompt.txt"
|
|
417
|
-
mkdir -p "$NORMAL_ROOT"
|
|
418
|
-
cd "$NORMAL_ROOT"
|
|
419
|
-
git init -q
|
|
420
|
-
write_session "$NORMAL_SESSION" "$NORMAL_ROOT" "$STARTUP_DISCUSSION"
|
|
421
|
-
|
|
422
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$NORMAL_FALLBACK" \
|
|
423
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=interactive \
|
|
424
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NORMAL_PROMPT" \
|
|
425
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$NORMAL_CLASSIFIER_OUTPUT" \
|
|
426
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$NORMAL_CLASSIFIER" \
|
|
427
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$NORMAL_ROUTING" \
|
|
428
|
-
pi --session "$NORMAL_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$NORMAL_ROUTER_TEXT" \
|
|
429
|
-
>"$TMPDIR/pi-cook-trigger-normal.out" 2>"$TMPDIR/pi-cook-trigger-normal.err"
|
|
430
|
-
|
|
431
|
-
python3 - "$NORMAL_ROUTING" "$NORMAL_CLASSIFIER" "$NORMAL_FALLBACK" "$NORMAL_PROMPT" "$NORMAL_ROUTER_TEXT" <<'PY'
|
|
432
|
-
import json
|
|
433
|
-
import sys
|
|
434
|
-
from pathlib import Path
|
|
435
|
-
|
|
436
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
437
|
-
classifier = json.loads(Path(sys.argv[2]).read_text())
|
|
438
|
-
fallback = json.loads(Path(sys.argv[3]).read_text())
|
|
439
|
-
driver_prompt = Path(sys.argv[4])
|
|
440
|
-
normal_text = sys.argv[5]
|
|
441
|
-
|
|
442
|
-
assert routing['action'] == 'continue', 'normal prompts should pass through to the main agent path'
|
|
443
|
-
assert routing['reason'] == 'classifier_normal_prompt', 'normal prompts should record the classifier_normal_prompt routing reason'
|
|
444
|
-
assert routing['classificationDecision'] == 'normal_prompt', 'normal prompts should snapshot the normal_prompt classifier decision'
|
|
445
|
-
assert routing['workflowBias'] == 'unknown', 'normal prompts should preserve the unknown workflow bias'
|
|
446
|
-
assert classifier['result']['status'] == 'classified', 'normal prompt pass-through should snapshot a classified trigger result'
|
|
447
|
-
assert classifier['result']['classification']['decision'] == 'normal_prompt', 'normal prompt pass-through should preserve the normal_prompt decision'
|
|
448
|
-
assert fallback['source'] == 'interactive', 'normal prompt pass-through should reach a later interactive fallback handler'
|
|
449
|
-
assert fallback['text'] == normal_text, 'normal prompt pass-through should preserve the original prompt text'
|
|
450
|
-
assert not Path('.agent').exists(), 'normal prompt pass-through should not bootstrap canonical workflow state'
|
|
451
|
-
assert not driver_prompt.exists(), 'normal prompt pass-through should not queue a /cook driver prompt'
|
|
452
|
-
PY
|
|
453
|
-
|
|
454
|
-
# Resume offers should keep the active workflow on the shared canonical resume path.
|
|
455
|
-
RESUME_ROOT="$TMPDIR/resume-repo"
|
|
456
|
-
RESUME_SESSION="$TMPDIR/resume-session.jsonl"
|
|
457
|
-
RESUME_PROMPT="$TMPDIR/resume-driver-prompt.txt"
|
|
458
|
-
RESUME_ROUTING="$TMPDIR/resume-routing.json"
|
|
459
|
-
RESUME_CLASSIFIER="$TMPDIR/resume-classifier.json"
|
|
460
|
-
RESUME_CONFIRMATION="$TMPDIR/resume-confirmation.json"
|
|
461
|
-
RESUME_FALLBACK="$TMPDIR/resume-fallback.json"
|
|
462
|
-
mkdir -p "$RESUME_ROOT"
|
|
463
|
-
cd "$RESUME_ROOT"
|
|
464
|
-
git init -q
|
|
465
|
-
write_completion_state "$RESUME_ROOT" "$ACTIVE_MISSION" continue false implement completion-implementer "Implement the active workflow slice"
|
|
466
|
-
write_session "$RESUME_SESSION" "$RESUME_ROOT" "$STARTUP_DISCUSSION"
|
|
467
|
-
|
|
468
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$RESUME_FALLBACK" \
|
|
469
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=interactive \
|
|
470
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
471
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$RESUME_PROMPT" \
|
|
472
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$RESUME_CLASSIFIER_OUTPUT" \
|
|
473
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$RESUME_CLASSIFIER" \
|
|
474
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
475
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH="$RESUME_CONFIRMATION" \
|
|
476
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$RESUME_ROUTING" \
|
|
477
|
-
pi --session "$RESUME_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$RESUME_ROUTER_TEXT" \
|
|
478
|
-
>"$TMPDIR/pi-cook-trigger-resume.out" 2>"$TMPDIR/pi-cook-trigger-resume.err"
|
|
479
|
-
|
|
480
|
-
python3 - "$RESUME_PROMPT" "$RESUME_ROUTING" "$RESUME_CLASSIFIER" "$RESUME_CONFIRMATION" "$RESUME_FALLBACK" "$ACTIVE_MISSION" "$RESUME_ROUTER_TEXT" <<'PY'
|
|
481
|
-
import json
|
|
482
|
-
import sys
|
|
483
|
-
from pathlib import Path
|
|
484
|
-
|
|
485
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
486
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
487
|
-
classifier = json.loads(Path(sys.argv[3]).read_text())
|
|
488
|
-
confirmation = json.loads(Path(sys.argv[4]).read_text())
|
|
489
|
-
fallback = Path(sys.argv[5])
|
|
490
|
-
mission = sys.argv[6]
|
|
491
|
-
trigger_text = sys.argv[7]
|
|
492
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
493
|
-
|
|
494
|
-
assert routing['action'] == 'routed_to_cook', 'resume handoff should route into the shared /cook entry'
|
|
495
|
-
assert routing['workflowBias'] == 'resume', 'resume handoff should preserve the resume routing bias'
|
|
496
|
-
assert routing['confirmationAction'] == 'start_workflow', 'resume handoff should record the workflow confirmation action'
|
|
497
|
-
assert confirmation['title'] == 'Resume the current completion workflow?', 'resume handoff should show the resume-specific workflow offer'
|
|
498
|
-
assert confirmation['actions'][0]['label'] == 'Resume workflow', 'resume handoff should show the resume-specific primary action label'
|
|
499
|
-
assert classifier['result']['classification']['workflowBias'] == 'resume', 'resume classifier snapshot should preserve the resume bias'
|
|
500
|
-
assert not fallback.exists(), 'resume handoff should keep the original interactive input away from later fallback handlers'
|
|
501
|
-
assert 'Resume the completion workflow from canonical state.' in prompt, 'resume handoff should queue the shared canonical resume prompt'
|
|
502
|
-
assert 'Natural-language handoff metadata:' in prompt, 'resume handoff should pass structured handoff metadata into the resume prompt'
|
|
503
|
-
assert '- preferred_routing_bias: resume' in prompt, 'resume handoff should preserve the resume bias in the resume prompt'
|
|
504
|
-
assert f'- trigger_text: {trigger_text}' in prompt, 'resume handoff should preserve the trigger text in the resume prompt'
|
|
505
|
-
assert state['mission_anchor'] == mission, 'resume handoff should preserve the active mission anchor'
|
|
506
|
-
PY
|
|
507
|
-
|
|
508
|
-
# Refocus offers should keep the chooser semantics inside the shared /cook entry before rewriting canonical state.
|
|
509
|
-
REFOCUS_ROOT="$TMPDIR/refocus-repo"
|
|
510
|
-
REFOCUS_SESSION="$TMPDIR/refocus-session.jsonl"
|
|
511
|
-
REFOCUS_PROMPT="$TMPDIR/refocus-driver-prompt.txt"
|
|
512
|
-
REFOCUS_ROUTING="$TMPDIR/refocus-routing.json"
|
|
513
|
-
REFOCUS_CLASSIFIER="$TMPDIR/refocus-classifier.json"
|
|
514
|
-
REFOCUS_CONFIRMATION="$TMPDIR/refocus-confirmation.json"
|
|
515
|
-
REFOCUS_CHOOSER="$TMPDIR/refocus-chooser.json"
|
|
516
|
-
mkdir -p "$REFOCUS_ROOT"
|
|
517
|
-
cd "$REFOCUS_ROOT"
|
|
518
|
-
git init -q
|
|
519
|
-
write_completion_state "$REFOCUS_ROOT" "$ACTIVE_MISSION" continue false implement completion-implementer "Implement the active workflow slice"
|
|
520
|
-
write_session "$REFOCUS_SESSION" "$REFOCUS_ROOT" "$REFOCUS_DISCUSSION"
|
|
521
|
-
|
|
522
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
523
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
524
|
-
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
525
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
526
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$REFOCUS_PROMPT" \
|
|
527
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$REFOCUS_CHOOSER" \
|
|
528
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$REFOCUS_CLASSIFIER_OUTPUT" \
|
|
529
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$REFOCUS_CLASSIFIER" \
|
|
530
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
531
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH="$REFOCUS_CONFIRMATION" \
|
|
532
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$REFOCUS_ROUTING" \
|
|
533
|
-
pi --session "$REFOCUS_SESSION" -e "$PKG_ROOT" -p "$REFOCUS_ROUTER_TEXT" \
|
|
534
|
-
>"$TMPDIR/pi-cook-trigger-refocus.out" 2>"$TMPDIR/pi-cook-trigger-refocus.err"
|
|
535
|
-
|
|
536
|
-
python3 - "$REFOCUS_PROMPT" "$REFOCUS_ROUTING" "$REFOCUS_CLASSIFIER" "$REFOCUS_CONFIRMATION" "$REFOCUS_CHOOSER" "$REFOCUS_MISSION" "$REFOCUS_ROUTER_TEXT" <<'PY'
|
|
537
|
-
import json
|
|
538
|
-
import sys
|
|
539
|
-
from pathlib import Path
|
|
540
|
-
|
|
541
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
542
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
543
|
-
classifier = json.loads(Path(sys.argv[3]).read_text())
|
|
544
|
-
confirmation = json.loads(Path(sys.argv[4]).read_text())
|
|
545
|
-
chooser = json.loads(Path(sys.argv[5]).read_text())
|
|
546
|
-
mission = sys.argv[6]
|
|
547
|
-
trigger_text = sys.argv[7]
|
|
548
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
549
|
-
|
|
550
|
-
assert routing['action'] == 'routed_to_cook', 'refocus handoff should route into the shared /cook entry'
|
|
551
|
-
assert routing['workflowBias'] == 'refocus', 'refocus handoff should preserve the refocus routing bias'
|
|
552
|
-
assert confirmation['title'] == 'Refocus the completion workflow from the recent discussion?', 'refocus handoff should show the refocus-specific workflow offer'
|
|
553
|
-
assert confirmation['actions'][0]['label'] == 'Refocus workflow', 'refocus handoff should show the refocus-specific primary action label'
|
|
554
|
-
assert classifier['result']['classification']['workflowBias'] == 'refocus', 'refocus classifier snapshot should preserve the refocus bias'
|
|
555
|
-
assert chooser['candidateMissions'][0] == mission, 'refocus chooser snapshot should preserve the replacement mission'
|
|
556
|
-
assert 'Start or continue the completion workflow for this repo.' in prompt, 'refocus handoff should queue the shared completion driver prompt'
|
|
557
|
-
assert 'Natural-language handoff metadata:' in prompt, 'refocus handoff should pass structured handoff metadata into the shared driver prompt'
|
|
558
|
-
assert '- preferred_routing_bias: refocus' in prompt, 'refocus handoff should preserve the refocus bias in the shared driver prompt'
|
|
559
|
-
assert f'- trigger_text: {trigger_text}' in prompt, 'refocus handoff should preserve the trigger text in the shared driver prompt'
|
|
560
|
-
assert state['mission_anchor'] == mission, 'refocus handoff should rewrite canonical mission state only through the shared /cook entry'
|
|
561
|
-
PY
|
|
562
|
-
|
|
563
|
-
# Next-round offers should start a new workflow round from recent discussion after a completed workflow.
|
|
564
|
-
NEXT_ROOT="$TMPDIR/next-round-repo"
|
|
565
|
-
NEXT_SESSION="$TMPDIR/next-round-session.jsonl"
|
|
566
|
-
NEXT_PROMPT="$TMPDIR/next-round-driver-prompt.txt"
|
|
567
|
-
NEXT_ROUTING="$TMPDIR/next-round-routing.json"
|
|
568
|
-
NEXT_CLASSIFIER="$TMPDIR/next-round-classifier.json"
|
|
569
|
-
NEXT_CONFIRMATION="$TMPDIR/next-round-confirmation.json"
|
|
570
|
-
mkdir -p "$NEXT_ROOT"
|
|
571
|
-
cd "$NEXT_ROOT"
|
|
572
|
-
git init -q
|
|
573
|
-
write_completion_state "$NEXT_ROOT" "$ACTIVE_MISSION" done true done null null
|
|
574
|
-
write_session "$NEXT_SESSION" "$NEXT_ROOT" "$NEXT_ROUND_DISCUSSION"
|
|
575
|
-
|
|
576
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
577
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
578
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
579
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$NEXT_PROMPT" \
|
|
580
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$NEXT_ROUND_CLASSIFIER_OUTPUT" \
|
|
581
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$NEXT_CLASSIFIER" \
|
|
582
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
583
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRMATION_PATH="$NEXT_CONFIRMATION" \
|
|
584
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$NEXT_ROUTING" \
|
|
585
|
-
pi --session "$NEXT_SESSION" -e "$PKG_ROOT" -p "$NEXT_ROUND_ROUTER_TEXT" \
|
|
586
|
-
>"$TMPDIR/pi-cook-trigger-next-round.out" 2>"$TMPDIR/pi-cook-trigger-next-round.err"
|
|
587
|
-
|
|
588
|
-
python3 - "$NEXT_PROMPT" "$NEXT_ROUTING" "$NEXT_CLASSIFIER" "$NEXT_CONFIRMATION" "$NEXT_ROUND_MISSION" "$NEXT_ROUND_ROUTER_TEXT" <<'PY'
|
|
589
|
-
import json
|
|
590
|
-
import sys
|
|
591
|
-
from pathlib import Path
|
|
592
|
-
|
|
593
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
594
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
595
|
-
classifier = json.loads(Path(sys.argv[3]).read_text())
|
|
596
|
-
confirmation = json.loads(Path(sys.argv[4]).read_text())
|
|
597
|
-
mission = sys.argv[5]
|
|
598
|
-
trigger_text = sys.argv[6]
|
|
599
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
600
|
-
|
|
601
|
-
assert routing['action'] == 'routed_to_cook', 'next-round handoff should route into the shared /cook entry'
|
|
602
|
-
assert routing['workflowBias'] == 'next_round', 'next-round handoff should preserve the next_round routing bias'
|
|
603
|
-
assert confirmation['title'] == 'Start the next completion workflow round from the recent discussion?', 'next-round handoff should show the next-round-specific workflow offer'
|
|
604
|
-
assert confirmation['actions'][0]['label'] == 'Start next round', 'next-round handoff should show the next-round-specific primary action label'
|
|
605
|
-
assert classifier['result']['classification']['workflowBias'] == 'next_round', 'next-round classifier snapshot should preserve the next_round bias'
|
|
606
|
-
assert 'Natural-language handoff metadata:' in prompt, 'next-round handoff should pass structured handoff metadata into the shared driver prompt'
|
|
607
|
-
assert '- preferred_routing_bias: next_round' in prompt, 'next-round handoff should preserve the next_round bias in the shared driver prompt'
|
|
608
|
-
assert f'- trigger_text: {trigger_text}' in prompt, 'next-round handoff should preserve the trigger text in the shared driver prompt'
|
|
609
|
-
assert state['mission_anchor'] == mission, 'next-round handoff should start a new mission anchor through the shared /cook entry'
|
|
610
|
-
PY
|
|
611
|
-
|
|
612
|
-
# Unclear low-confidence commandless inputs should clarify instead of silently falling through.
|
|
613
|
-
UNCLEAR_ROOT="$TMPDIR/unclear-repo"
|
|
614
|
-
UNCLEAR_SESSION="$TMPDIR/unclear-session.jsonl"
|
|
615
|
-
UNCLEAR_PROMPT="$TMPDIR/unclear-driver-prompt.txt"
|
|
616
|
-
UNCLEAR_ROUTING="$TMPDIR/unclear-routing.json"
|
|
617
|
-
UNCLEAR_CLASSIFIER="$TMPDIR/unclear-classifier.json"
|
|
618
|
-
UNCLEAR_CLARIFICATION="$TMPDIR/unclear-clarification.json"
|
|
619
|
-
mkdir -p "$UNCLEAR_ROOT"
|
|
620
|
-
cd "$UNCLEAR_ROOT"
|
|
621
|
-
git init -q
|
|
622
|
-
write_completion_state "$UNCLEAR_ROOT" "$ACTIVE_MISSION" continue false implement completion-implementer "Implement the active workflow slice"
|
|
623
|
-
write_session "$UNCLEAR_SESSION" "$UNCLEAR_ROOT" "$REFOCUS_DISCUSSION"
|
|
624
|
-
|
|
625
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
626
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
627
|
-
PI_COMPLETION_EXISTING_WORKFLOW_ACTION=refocus \
|
|
628
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
629
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$UNCLEAR_PROMPT" \
|
|
630
|
-
PI_COMPLETION_TEST_EXISTING_WORKFLOW_CHOOSER_PATH="$REFOCUS_CHOOSER" \
|
|
631
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$UNCLEAR_CLASSIFIER_OUTPUT" \
|
|
632
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$UNCLEAR_CLASSIFIER" \
|
|
633
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_ACTION=refocus \
|
|
634
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_PATH="$UNCLEAR_CLARIFICATION" \
|
|
635
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$UNCLEAR_ROUTING" \
|
|
636
|
-
pi --session "$UNCLEAR_SESSION" -e "$PKG_ROOT" -p "$UNCLEAR_ROUTER_TEXT" \
|
|
637
|
-
>"$TMPDIR/pi-cook-trigger-unclear.out" 2>"$TMPDIR/pi-cook-trigger-unclear.err"
|
|
638
|
-
|
|
639
|
-
python3 - "$UNCLEAR_PROMPT" "$UNCLEAR_ROUTING" "$UNCLEAR_CLASSIFIER" "$UNCLEAR_CLARIFICATION" "$REFOCUS_MISSION" <<'PY'
|
|
640
|
-
import json
|
|
641
|
-
import sys
|
|
642
|
-
from pathlib import Path
|
|
643
|
-
|
|
644
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
645
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
646
|
-
classifier = json.loads(Path(sys.argv[3]).read_text())
|
|
647
|
-
clarification = json.loads(Path(sys.argv[4]).read_text())
|
|
648
|
-
mission = sys.argv[5]
|
|
649
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
650
|
-
|
|
651
|
-
assert routing['action'] == 'routed_to_cook', 'unclear commandless routing should resolve through clarification instead of silently continuing'
|
|
652
|
-
assert routing['reason'] == 'clarification_resolved', 'unclear commandless routing should record clarification_resolved'
|
|
653
|
-
assert routing['classificationDecision'] == 'unclear', 'unclear routing should preserve the unclear classifier decision'
|
|
654
|
-
assert routing['clarificationAction'] == 'route_refocus', 'unclear routing should record the selected clarification action'
|
|
655
|
-
assert routing['clarificationSelectedBias'] == 'refocus', 'unclear routing should preserve the clarification-selected routing bias'
|
|
656
|
-
assert routing['clarificationGoal'] == mission, 'unclear routing should preserve the clarified mission goal'
|
|
657
|
-
assert classifier['result']['classification']['decision'] == 'unclear', 'unclear classifier snapshot should preserve the unclear decision'
|
|
658
|
-
assert clarification['title'] == 'Clarify how the completion workflow should proceed', 'unclear routing should show the clarification chooser'
|
|
659
|
-
assert clarification['actions'][0]['id'] == 'route_resume', 'unclear active workflow clarification should offer resume first'
|
|
660
|
-
assert clarification['actions'][1]['id'] == 'route_refocus', 'unclear active workflow clarification should offer refocus'
|
|
661
|
-
assert clarification['actions'][2]['id'] == 'send_as_normal_chat', 'unclear clarification should expose send as normal chat before cancel'
|
|
662
|
-
assert 'Natural-language handoff metadata:' in prompt, 'clarified commandless routing should still pass structured handoff metadata into the shared driver prompt'
|
|
663
|
-
assert '- clarification_selected_bias: refocus' in prompt, 'clarified commandless routing should carry clarification bias into the shared driver prompt'
|
|
664
|
-
assert f'- clarification_goal: {mission}' in prompt, 'clarified commandless routing should carry the clarified mission goal into the shared driver prompt'
|
|
665
|
-
assert state['mission_anchor'] == mission, 'clarified refocus routing should still rewrite canonical state only through the shared /cook entry'
|
|
666
|
-
PY
|
|
667
|
-
|
|
668
|
-
# Clarification send as normal chat should replay the original message exactly once without rewriting canonical workflow state.
|
|
669
|
-
UNCLEAR_REPLAY_ROOT="$TMPDIR/unclear-replay-repo"
|
|
670
|
-
UNCLEAR_REPLAY_SESSION="$TMPDIR/unclear-replay-session.jsonl"
|
|
671
|
-
UNCLEAR_REPLAY_PROMPT="$TMPDIR/unclear-replay-driver-prompt.txt"
|
|
672
|
-
UNCLEAR_REPLAY_ROUTING="$TMPDIR/unclear-replay-routing.json"
|
|
673
|
-
UNCLEAR_REPLAY_CLARIFICATION="$TMPDIR/unclear-replay-clarification.json"
|
|
674
|
-
UNCLEAR_REPLAY_FALLBACK="$TMPDIR/unclear-replay-fallback.json"
|
|
675
|
-
mkdir -p "$UNCLEAR_REPLAY_ROOT"
|
|
676
|
-
cd "$UNCLEAR_REPLAY_ROOT"
|
|
677
|
-
git init -q
|
|
678
|
-
write_completion_state "$UNCLEAR_REPLAY_ROOT" "$ACTIVE_MISSION" continue false implement completion-implementer "Implement the active workflow slice"
|
|
679
|
-
write_session "$UNCLEAR_REPLAY_SESSION" "$UNCLEAR_REPLAY_ROOT" "$REFOCUS_DISCUSSION"
|
|
680
|
-
|
|
681
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$UNCLEAR_REPLAY_FALLBACK" \
|
|
682
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=any \
|
|
683
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
684
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
685
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$UNCLEAR_REPLAY_PROMPT" \
|
|
686
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$UNCLEAR_CLASSIFIER_OUTPUT" \
|
|
687
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_ACTION=send_as_normal_chat \
|
|
688
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_PATH="$UNCLEAR_REPLAY_CLARIFICATION" \
|
|
689
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$UNCLEAR_REPLAY_ROUTING" \
|
|
690
|
-
pi --session "$UNCLEAR_REPLAY_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$UNCLEAR_ROUTER_TEXT" \
|
|
691
|
-
>"$TMPDIR/pi-cook-trigger-unclear-replay.out" 2>"$TMPDIR/pi-cook-trigger-unclear-replay.err"
|
|
692
|
-
|
|
693
|
-
python3 - "$UNCLEAR_REPLAY_ROUTING" "$UNCLEAR_REPLAY_CLARIFICATION" "$UNCLEAR_REPLAY_FALLBACK" "$UNCLEAR_REPLAY_PROMPT" "$UNCLEAR_ROUTER_TEXT" "$ACTIVE_MISSION" "$TMPDIR/pi-cook-trigger-unclear-replay.out" "$TMPDIR/pi-cook-trigger-unclear-replay.err" <<'PY'
|
|
694
|
-
import json
|
|
695
|
-
import sys
|
|
696
|
-
from pathlib import Path
|
|
697
|
-
|
|
698
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
699
|
-
clarification = json.loads(Path(sys.argv[2]).read_text())
|
|
700
|
-
fallback = json.loads(Path(sys.argv[3]).read_text())
|
|
701
|
-
driver_prompt = Path(sys.argv[4])
|
|
702
|
-
trigger_text = sys.argv[5]
|
|
703
|
-
mission = sys.argv[6]
|
|
704
|
-
output = Path(sys.argv[7]).read_text() + Path(sys.argv[8]).read_text()
|
|
705
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
706
|
-
|
|
707
|
-
assert routing['action'] == 'handled', 'clarification send as normal chat should keep the original intercepted turn handled'
|
|
708
|
-
assert routing['reason'] == 'user_sent_as_normal_chat_after_clarification', 'clarification send as normal chat should record the explicit replay reason'
|
|
709
|
-
assert routing['clarificationAction'] == 'send_as_normal_chat', 'clarification send as normal chat should record the replay clarification action'
|
|
710
|
-
assert routing['replayedToPrimaryAgent'] is True, 'clarification send as normal chat should record that the original message was replayed'
|
|
711
|
-
assert routing['replayBypassMarkerApplied'] is True, 'clarification send as normal chat should record the router-bypass replay marker'
|
|
712
|
-
assert clarification['actions'][2]['label'] == 'Send as normal chat', 'clarification UI should expose send as normal chat instead of keep chatting'
|
|
713
|
-
assert fallback['source'] == 'extension', 'clarification send as normal chat should replay through an extension-originated bypass turn'
|
|
714
|
-
assert fallback['text'] == trigger_text, 'clarification send as normal chat should replay the original prompt text exactly once'
|
|
715
|
-
assert not driver_prompt.exists(), 'clarification send as normal chat should not queue a /cook driver prompt'
|
|
716
|
-
assert state['mission_anchor'] == mission, 'clarification send as normal chat should keep canonical workflow state unchanged'
|
|
717
|
-
assert 'bypassed router interception' in output, 'clarification send as normal chat should tell the user that the replay bypassed router interception'
|
|
718
|
-
PY
|
|
719
|
-
|
|
720
|
-
# Clarification cancel should fail closed without replaying the original message or rewriting canonical state.
|
|
721
|
-
UNCLEAR_CANCEL_ROOT="$TMPDIR/unclear-cancel-repo"
|
|
722
|
-
UNCLEAR_CANCEL_SESSION="$TMPDIR/unclear-cancel-session.jsonl"
|
|
723
|
-
UNCLEAR_CANCEL_PROMPT="$TMPDIR/unclear-cancel-driver-prompt.txt"
|
|
724
|
-
UNCLEAR_CANCEL_ROUTING="$TMPDIR/unclear-cancel-routing.json"
|
|
725
|
-
UNCLEAR_CANCEL_CLARIFICATION="$TMPDIR/unclear-cancel-clarification.json"
|
|
726
|
-
mkdir -p "$UNCLEAR_CANCEL_ROOT"
|
|
727
|
-
cd "$UNCLEAR_CANCEL_ROOT"
|
|
728
|
-
git init -q
|
|
729
|
-
write_completion_state "$UNCLEAR_CANCEL_ROOT" "$ACTIVE_MISSION" continue false implement completion-implementer "Implement the active workflow slice"
|
|
730
|
-
write_session "$UNCLEAR_CANCEL_SESSION" "$UNCLEAR_CANCEL_ROOT" "$REFOCUS_DISCUSSION"
|
|
731
|
-
|
|
732
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
733
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
734
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$UNCLEAR_CANCEL_PROMPT" \
|
|
735
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$UNCLEAR_CLASSIFIER_OUTPUT" \
|
|
736
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_ACTION=cancel \
|
|
737
|
-
PI_COMPLETION_TEST_TRIGGER_CLARIFICATION_PATH="$UNCLEAR_CANCEL_CLARIFICATION" \
|
|
738
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$UNCLEAR_CANCEL_ROUTING" \
|
|
739
|
-
pi --session "$UNCLEAR_CANCEL_SESSION" -e "$PKG_ROOT" -p "$UNCLEAR_ROUTER_TEXT" \
|
|
740
|
-
>"$TMPDIR/pi-cook-trigger-unclear-cancel.out" 2>"$TMPDIR/pi-cook-trigger-unclear-cancel.err"
|
|
741
|
-
|
|
742
|
-
python3 - "$UNCLEAR_CANCEL_ROUTING" "$UNCLEAR_CANCEL_PROMPT" "$TMPDIR/pi-cook-trigger-unclear-cancel.out" "$TMPDIR/pi-cook-trigger-unclear-cancel.err" "$ACTIVE_MISSION" <<'PY'
|
|
743
|
-
import json
|
|
744
|
-
import sys
|
|
745
|
-
from pathlib import Path
|
|
746
|
-
|
|
747
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
748
|
-
driver_prompt = Path(sys.argv[2])
|
|
749
|
-
output = Path(sys.argv[3]).read_text() + Path(sys.argv[4]).read_text()
|
|
750
|
-
mission = sys.argv[5]
|
|
751
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
752
|
-
|
|
753
|
-
assert routing['action'] == 'handled', 'clarification cancel should fail closed instead of continuing to the main agent'
|
|
754
|
-
assert routing['reason'] == 'user_cancelled_clarification', 'clarification cancel should record the user_cancelled_clarification reason'
|
|
755
|
-
assert routing['clarificationAction'] == 'cancel', 'clarification cancel should record the cancel action'
|
|
756
|
-
assert not driver_prompt.exists(), 'clarification cancel should not queue a /cook driver prompt'
|
|
757
|
-
assert 'rerun /cook explicitly' in output, 'clarification cancel should direct the user back to explicit /cook when needed'
|
|
758
|
-
assert state['mission_anchor'] == mission, 'clarification cancel should keep canonical state unchanged'
|
|
759
|
-
PY
|
|
760
|
-
|
|
761
|
-
# Explicit adoption of a recent assistant plan should carry adopted context into the shared /cook entry.
|
|
762
|
-
ADOPTED_PLAN_ROOT="$TMPDIR/adopted-plan-repo"
|
|
763
|
-
ADOPTED_PLAN_SESSION="$TMPDIR/adopted-plan-session.jsonl"
|
|
764
|
-
ADOPTED_PLAN_PROMPT="$TMPDIR/adopted-plan-driver-prompt.txt"
|
|
765
|
-
ADOPTED_PLAN_ROUTING="$TMPDIR/adopted-plan-routing.json"
|
|
766
|
-
mkdir -p "$ADOPTED_PLAN_ROOT"
|
|
767
|
-
cd "$ADOPTED_PLAN_ROOT"
|
|
768
|
-
git init -q
|
|
769
|
-
write_mixed_session "$ADOPTED_PLAN_SESSION" "$ADOPTED_PLAN_ROOT" "$STARTUP_DISCUSSION" "$STARTUP_DISCUSSION"
|
|
770
|
-
|
|
771
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
772
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
773
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
774
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$ADOPTED_PLAN_PROMPT" \
|
|
775
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
776
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
777
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$ADOPTED_PLAN_ROUTING" \
|
|
778
|
-
pi --session "$ADOPTED_PLAN_SESSION" -e "$PKG_ROOT" -p "就照剛剛那份方案做" \
|
|
779
|
-
>"$TMPDIR/pi-cook-trigger-adopted-plan.out" 2>"$TMPDIR/pi-cook-trigger-adopted-plan.err"
|
|
780
|
-
|
|
781
|
-
python3 - "$ADOPTED_PLAN_PROMPT" "$ADOPTED_PLAN_ROUTING" <<'PY'
|
|
782
|
-
import json
|
|
783
|
-
import sys
|
|
784
|
-
from pathlib import Path
|
|
785
|
-
|
|
786
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
787
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
788
|
-
|
|
789
|
-
assert routing['adoptedArtifactKind'] == 'recent_plan', 'explicit adoption of the recent assistant plan should surface as a recent_plan artifact'
|
|
790
|
-
assert routing['adoptedArtifactBasis'] == 'explicit_user_adoption', 'adopted recent plans should preserve the explicit_user_adoption basis'
|
|
791
|
-
assert '- adopted_artifact_kind: recent_plan' in prompt, 'adopted recent plans should be forwarded into the shared /cook entry metadata'
|
|
792
|
-
assert '- adopted_artifact_basis: explicit_user_adoption' in prompt, 'adopted recent plans should preserve their trust-boundary basis in the shared driver prompt'
|
|
793
|
-
assert '- adopted_artifact_title: latest discussed assistant plan' in prompt, 'adopted recent plans should include the adopted artifact title in the shared driver prompt'
|
|
794
|
-
assert '- adopted_artifact_preview:' in prompt, 'adopted recent plans should include preview context in the shared driver prompt'
|
|
795
|
-
PY
|
|
796
|
-
|
|
797
|
-
# Explicit adoption of a repo markdown artifact should carry the path into the shared /cook entry.
|
|
798
|
-
ADOPTED_MD_ROOT="$TMPDIR/adopted-md-repo"
|
|
799
|
-
ADOPTED_MD_SESSION="$TMPDIR/adopted-md-session.jsonl"
|
|
800
|
-
ADOPTED_MD_PROMPT="$TMPDIR/adopted-md-driver-prompt.txt"
|
|
801
|
-
ADOPTED_MD_ROUTING="$TMPDIR/adopted-md-routing.json"
|
|
802
|
-
mkdir -p "$ADOPTED_MD_ROOT/docs"
|
|
803
|
-
cd "$ADOPTED_MD_ROOT"
|
|
804
|
-
git init -q
|
|
805
|
-
cat > docs/plan.md <<'EOF'
|
|
806
|
-
# Plan
|
|
807
|
-
|
|
808
|
-
Mission: Route natural-language handoff into the shared /cook entry.
|
|
809
|
-
EOF
|
|
810
|
-
write_session "$ADOPTED_MD_SESSION" "$ADOPTED_MD_ROOT" "$STARTUP_DISCUSSION"
|
|
811
|
-
|
|
812
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
813
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
814
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
815
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$ADOPTED_MD_PROMPT" \
|
|
816
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
817
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
818
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$ADOPTED_MD_ROUTING" \
|
|
819
|
-
pi --session "$ADOPTED_MD_SESSION" -e "$PKG_ROOT" -p "照 docs/plan.md 開始" \
|
|
820
|
-
>"$TMPDIR/pi-cook-trigger-adopted-md.out" 2>"$TMPDIR/pi-cook-trigger-adopted-md.err"
|
|
821
|
-
|
|
822
|
-
python3 - "$ADOPTED_MD_PROMPT" "$ADOPTED_MD_ROUTING" <<'PY'
|
|
823
|
-
import json
|
|
824
|
-
import sys
|
|
825
|
-
from pathlib import Path
|
|
826
|
-
|
|
827
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
828
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
829
|
-
|
|
830
|
-
assert routing['adoptedArtifactKind'] == 'repo_markdown', 'explicit adoption of a repo markdown artifact should surface as repo_markdown'
|
|
831
|
-
assert routing['adoptedArtifactPath'] == 'docs/plan.md', 'repo markdown adoption should preserve the adopted path'
|
|
832
|
-
assert '- adopted_artifact_kind: repo_markdown' in prompt, 'repo markdown adoption should be forwarded into the shared /cook entry metadata'
|
|
833
|
-
assert '- adopted_artifact_path: docs/plan.md' in prompt, 'repo markdown adoption should preserve the adopted path in the shared driver prompt'
|
|
834
|
-
PY
|
|
835
|
-
|
|
836
|
-
# An unresolved explicit repo markdown path must fail closed instead of falling back to recent-plan metadata.
|
|
837
|
-
MISSING_MD_ROOT="$TMPDIR/missing-md-repo"
|
|
838
|
-
MISSING_MD_SESSION="$TMPDIR/missing-md-session.jsonl"
|
|
839
|
-
MISSING_MD_PROMPT="$TMPDIR/missing-md-driver-prompt.txt"
|
|
840
|
-
MISSING_MD_ROUTING="$TMPDIR/missing-md-routing.json"
|
|
841
|
-
mkdir -p "$MISSING_MD_ROOT"
|
|
842
|
-
cd "$MISSING_MD_ROOT"
|
|
843
|
-
git init -q
|
|
844
|
-
write_mixed_session "$MISSING_MD_SESSION" "$MISSING_MD_ROOT" "$STARTUP_DISCUSSION" "$STARTUP_DISCUSSION"
|
|
845
|
-
|
|
846
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
847
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
848
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
849
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$MISSING_MD_PROMPT" \
|
|
850
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
851
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
852
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$MISSING_MD_ROUTING" \
|
|
853
|
-
pi --session "$MISSING_MD_SESSION" -e "$PKG_ROOT" -p "use docs/missing.md" \
|
|
854
|
-
>"$TMPDIR/pi-cook-trigger-missing-md.out" 2>"$TMPDIR/pi-cook-trigger-missing-md.err"
|
|
855
|
-
|
|
856
|
-
python3 - "$MISSING_MD_PROMPT" "$MISSING_MD_ROUTING" <<'PY'
|
|
857
|
-
import json
|
|
858
|
-
import sys
|
|
859
|
-
from pathlib import Path
|
|
860
|
-
|
|
861
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
862
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
863
|
-
|
|
864
|
-
assert routing['adoptedArtifactKind'] is None, 'unresolved explicit repo markdown paths must not fall back to recent_plan adoption metadata'
|
|
865
|
-
assert routing['adoptedArtifactBasis'] is None, 'unresolved explicit repo markdown paths must not preserve adopted-artifact trust metadata'
|
|
866
|
-
assert '- adopted_artifact_kind:' not in prompt, 'unresolved explicit repo markdown paths must not be elevated into the shared /cook handoff metadata'
|
|
867
|
-
assert '- adopted_artifact_basis:' not in prompt, 'unresolved explicit repo markdown paths must not preserve adopted-artifact basis metadata in the shared driver prompt'
|
|
868
|
-
PY
|
|
869
|
-
|
|
870
|
-
# Unadopted assistant plans should remain background only and should not be elevated into handoff context.
|
|
871
|
-
UNADOPTED_PLAN_ROOT="$TMPDIR/unadopted-plan-repo"
|
|
872
|
-
UNADOPTED_PLAN_SESSION="$TMPDIR/unadopted-plan-session.jsonl"
|
|
873
|
-
UNADOPTED_PLAN_PROMPT="$TMPDIR/unadopted-plan-driver-prompt.txt"
|
|
874
|
-
UNADOPTED_PLAN_ROUTING="$TMPDIR/unadopted-plan-routing.json"
|
|
875
|
-
mkdir -p "$UNADOPTED_PLAN_ROOT"
|
|
876
|
-
cd "$UNADOPTED_PLAN_ROOT"
|
|
877
|
-
git init -q
|
|
878
|
-
write_mixed_session "$UNADOPTED_PLAN_SESSION" "$UNADOPTED_PLAN_ROOT" "$STARTUP_DISCUSSION" "$STARTUP_DISCUSSION"
|
|
879
|
-
|
|
880
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
881
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
882
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
883
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$UNADOPTED_PLAN_PROMPT" \
|
|
884
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$STARTUP_CLASSIFIER_OUTPUT" \
|
|
885
|
-
PI_COMPLETION_TEST_TRIGGER_CONFIRM_ACTION=start \
|
|
886
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$UNADOPTED_PLAN_ROUTING" \
|
|
887
|
-
pi --session "$UNADOPTED_PLAN_SESSION" -e "$PKG_ROOT" -p "開始做" \
|
|
888
|
-
>"$TMPDIR/pi-cook-trigger-unadopted-plan.out" 2>"$TMPDIR/pi-cook-trigger-unadopted-plan.err"
|
|
889
|
-
|
|
890
|
-
python3 - "$UNADOPTED_PLAN_PROMPT" "$UNADOPTED_PLAN_ROUTING" <<'PY'
|
|
891
|
-
import json
|
|
892
|
-
import sys
|
|
893
|
-
from pathlib import Path
|
|
894
|
-
|
|
895
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
896
|
-
routing = json.loads(Path(sys.argv[2]).read_text())
|
|
897
|
-
|
|
898
|
-
assert routing['adoptedArtifactKind'] is None, 'unadopted assistant plans must stay background-only in routing snapshots'
|
|
899
|
-
assert '- adopted_artifact_kind:' not in prompt, 'unadopted assistant plans must not be elevated into the shared /cook handoff metadata'
|
|
900
|
-
PY
|
|
901
|
-
|
|
902
|
-
# Extension-originated turns should bypass natural-language routing and continue unchanged.
|
|
903
|
-
EXT_ROOT="$TMPDIR/extension-source-repo"
|
|
904
|
-
EXT_ROUTING="$TMPDIR/extension-source-routing.json"
|
|
905
|
-
EXT_FALLBACK="$TMPDIR/extension-source-fallback.json"
|
|
906
|
-
EXT_CLASSIFIER="$TMPDIR/extension-source-classifier.json"
|
|
907
|
-
EXT_PROMPT="$TMPDIR/extension-source-driver-prompt.txt"
|
|
908
|
-
mkdir -p "$EXT_ROOT"
|
|
909
|
-
cd "$EXT_ROOT"
|
|
910
|
-
git init -q
|
|
911
|
-
|
|
912
|
-
PI_COOK_TRIGGER_EXTENSION_SOURCE_TEXT="開始做" \
|
|
913
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$EXT_FALLBACK" \
|
|
914
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=extension \
|
|
915
|
-
PI_COMPLETION_TEST_TRIGGER_MODE=router \
|
|
916
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_OUTPUT="$NORMAL_CLASSIFIER_OUTPUT" \
|
|
917
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$EXT_PROMPT" \
|
|
918
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$EXT_CLASSIFIER" \
|
|
919
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$EXT_ROUTING" \
|
|
920
|
-
pi -e "$PKG_ROOT" -e "$SENDER_EXTENSION" -e "$FALLBACK_EXTENSION" -p "__seed__" \
|
|
921
|
-
>"$TMPDIR/pi-cook-trigger-extension-source.out" 2>"$TMPDIR/pi-cook-trigger-extension-source.err"
|
|
922
|
-
|
|
923
|
-
python3 - "$EXT_ROUTING" "$EXT_FALLBACK" "$EXT_CLASSIFIER" "$EXT_PROMPT" <<'PY'
|
|
924
|
-
import json
|
|
925
|
-
import sys
|
|
926
|
-
from pathlib import Path
|
|
927
|
-
|
|
928
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
929
|
-
fallback = json.loads(Path(sys.argv[2]).read_text())
|
|
930
|
-
classifier = Path(sys.argv[3])
|
|
931
|
-
driver_prompt = Path(sys.argv[4])
|
|
932
|
-
|
|
933
|
-
assert routing['action'] == 'continue', 'extension-originated turns should bypass natural-language routing'
|
|
934
|
-
assert routing['reason'] == 'extension_source', 'extension-originated turns should record the extension_source bypass reason'
|
|
935
|
-
assert fallback['source'] == 'extension', 'extension-originated turns should continue to later extension-source handlers'
|
|
936
|
-
assert fallback['text'] == '開始做', 'extension-originated turns should preserve the original extension text'
|
|
937
|
-
assert not driver_prompt.exists(), 'extension-originated turns should not queue a /cook driver prompt'
|
|
938
|
-
assert not Path('.agent').exists(), 'extension-originated turns should not bootstrap canonical workflow state'
|
|
939
|
-
PY
|
|
940
|
-
|
|
941
|
-
# Explicit /cook command entry should continue through the command path without the input hook interfering.
|
|
942
|
-
COOK_ROOT="$TMPDIR/explicit-cook-repo"
|
|
943
|
-
COOK_SESSION="$TMPDIR/explicit-cook-session.jsonl"
|
|
944
|
-
COOK_ROUTING="$TMPDIR/explicit-cook-routing.json"
|
|
945
|
-
COOK_PROMPT="$TMPDIR/explicit-cook-driver-prompt.txt"
|
|
946
|
-
mkdir -p "$COOK_ROOT"
|
|
947
|
-
cd "$COOK_ROOT"
|
|
948
|
-
git init -q
|
|
949
|
-
write_session "$COOK_SESSION" "$COOK_ROOT" "$STARTUP_DISCUSSION"
|
|
950
|
-
|
|
951
|
-
PI_COMPLETION_CONTEXT_PROPOSAL_ACTION=accept \
|
|
952
|
-
PI_COMPLETION_DISABLE_CONTEXT_PROPOSAL_ANALYST=1 \
|
|
953
|
-
PI_COMPLETION_SKIP_DRIVER_KICKOFF=1 \
|
|
954
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$COOK_PROMPT" \
|
|
955
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$COOK_ROUTING" \
|
|
956
|
-
pi --session "$COOK_SESSION" -e "$PKG_ROOT" -p "/cook" \
|
|
957
|
-
>"$TMPDIR/pi-cook-trigger-explicit-cook.out" 2>"$TMPDIR/pi-cook-trigger-explicit-cook.err"
|
|
958
|
-
|
|
959
|
-
python3 - "$COOK_PROMPT" "$COOK_ROUTING" "$STARTUP_MISSION" <<'PY'
|
|
960
|
-
import json
|
|
961
|
-
import sys
|
|
962
|
-
from pathlib import Path
|
|
963
|
-
|
|
964
|
-
prompt = Path(sys.argv[1]).read_text()
|
|
965
|
-
routing = Path(sys.argv[2])
|
|
966
|
-
mission = sys.argv[3]
|
|
967
|
-
state = json.loads(Path('.agent/state.json').read_text())
|
|
968
|
-
|
|
969
|
-
assert 'Start or continue the completion workflow for this repo.' in prompt, 'explicit /cook should keep queuing the shared completion driver prompt'
|
|
970
|
-
assert not routing.exists(), 'explicit /cook should bypass the natural-language input-routing snapshot entirely'
|
|
971
|
-
assert state['mission_anchor'] == mission, 'explicit /cook should keep the existing startup behavior'
|
|
972
|
-
PY
|
|
973
|
-
|
|
974
|
-
# Classifier timeout should surface recovery UI and allow explicit send-as-normal-chat replay.
|
|
975
|
-
TIMEOUT_ROOT="$TMPDIR/timeout-repo"
|
|
976
|
-
TIMEOUT_SESSION="$TMPDIR/timeout-session.jsonl"
|
|
977
|
-
TIMEOUT_ROUTING="$TMPDIR/timeout-routing.json"
|
|
978
|
-
TIMEOUT_CLASSIFIER="$TMPDIR/timeout-classifier.json"
|
|
979
|
-
TIMEOUT_FALLBACK="$TMPDIR/timeout-fallback.json"
|
|
980
|
-
TIMEOUT_PROMPT="$TMPDIR/timeout-driver-prompt.txt"
|
|
981
|
-
TIMEOUT_RECOVERY="$TMPDIR/timeout-recovery.json"
|
|
982
|
-
mkdir -p "$TIMEOUT_ROOT"
|
|
983
|
-
cd "$TIMEOUT_ROOT"
|
|
984
|
-
git init -q
|
|
985
|
-
write_session "$TIMEOUT_SESSION" "$TIMEOUT_ROOT" "$STARTUP_DISCUSSION"
|
|
986
|
-
|
|
987
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$TIMEOUT_FALLBACK" \
|
|
988
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=any \
|
|
989
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$TIMEOUT_PROMPT" \
|
|
990
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_FAILURE=timeout \
|
|
991
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$TIMEOUT_CLASSIFIER" \
|
|
992
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_ACTION=send_as_normal_chat \
|
|
993
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_PATH="$TIMEOUT_RECOVERY" \
|
|
994
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$TIMEOUT_ROUTING" \
|
|
995
|
-
pi --session "$TIMEOUT_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$STARTUP_ROUTER_TEXT" \
|
|
996
|
-
>"$TMPDIR/pi-cook-trigger-timeout.out" 2>"$TMPDIR/pi-cook-trigger-timeout.err"
|
|
997
|
-
|
|
998
|
-
python3 - "$TIMEOUT_ROUTING" "$TIMEOUT_CLASSIFIER" "$TIMEOUT_FALLBACK" "$TIMEOUT_PROMPT" "$TIMEOUT_RECOVERY" "$STARTUP_ROUTER_TEXT" "$TMPDIR/pi-cook-trigger-timeout.out" "$TMPDIR/pi-cook-trigger-timeout.err" <<'PY'
|
|
999
|
-
import json
|
|
1000
|
-
import sys
|
|
1001
|
-
from pathlib import Path
|
|
1002
|
-
|
|
1003
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
1004
|
-
classifier = json.loads(Path(sys.argv[2]).read_text())
|
|
1005
|
-
fallback = json.loads(Path(sys.argv[3]).read_text())
|
|
1006
|
-
driver_prompt = Path(sys.argv[4])
|
|
1007
|
-
recovery = json.loads(Path(sys.argv[5]).read_text())
|
|
1008
|
-
trigger_text = sys.argv[6]
|
|
1009
|
-
output = Path(sys.argv[7]).read_text() + Path(sys.argv[8]).read_text()
|
|
1010
|
-
|
|
1011
|
-
assert routing['action'] == 'handled', 'classifier timeout recovery should keep the original intercepted turn handled'
|
|
1012
|
-
assert routing['reason'] == 'classifier_timeout_send_as_normal_chat', 'classifier timeout recovery should record the explicit replay outcome'
|
|
1013
|
-
assert routing['recoveryAction'] == 'send_as_normal_chat', 'classifier timeout recovery should record the chosen recovery action'
|
|
1014
|
-
assert routing['replayedToPrimaryAgent'] is True, 'classifier timeout recovery should record that the original message was replayed'
|
|
1015
|
-
assert routing['replayBypassMarkerApplied'] is True, 'classifier timeout recovery should record the router-bypass replay marker'
|
|
1016
|
-
assert classifier['result']['status'] == 'timeout', 'classifier timeout should snapshot the timeout result'
|
|
1017
|
-
assert recovery['actions'][0]['id'] == 'retry_routing', 'classifier timeout recovery should offer retry routing first'
|
|
1018
|
-
assert recovery['actions'][1]['id'] == 'send_as_normal_chat', 'classifier timeout recovery should offer send as normal chat'
|
|
1019
|
-
assert fallback['source'] == 'extension', 'classifier timeout send as normal chat should replay through an extension-originated bypass turn'
|
|
1020
|
-
assert fallback['text'] == trigger_text, 'classifier timeout send as normal chat should replay the original prompt text exactly once'
|
|
1021
|
-
assert not driver_prompt.exists(), 'classifier timeout send as normal chat should not queue a /cook driver prompt'
|
|
1022
|
-
assert not Path('.agent').exists(), 'classifier timeout send as normal chat should not bootstrap canonical workflow state'
|
|
1023
|
-
assert 'bypassed router interception' in output, 'classifier timeout recovery should tell the user that the replay bypassed router interception'
|
|
1024
|
-
PY
|
|
1025
|
-
|
|
1026
|
-
# Invalid classifier output should surface recovery UI and stay fail-closed on cancel.
|
|
1027
|
-
INVALID_ROOT="$TMPDIR/invalid-output-repo"
|
|
1028
|
-
INVALID_SESSION="$TMPDIR/invalid-output-session.jsonl"
|
|
1029
|
-
INVALID_ROUTING="$TMPDIR/invalid-output-routing.json"
|
|
1030
|
-
INVALID_CLASSIFIER="$TMPDIR/invalid-output-classifier.json"
|
|
1031
|
-
INVALID_FALLBACK="$TMPDIR/invalid-output-fallback.json"
|
|
1032
|
-
INVALID_PROMPT="$TMPDIR/invalid-output-driver-prompt.txt"
|
|
1033
|
-
INVALID_RECOVERY="$TMPDIR/invalid-output-recovery.json"
|
|
1034
|
-
mkdir -p "$INVALID_ROOT"
|
|
1035
|
-
cd "$INVALID_ROOT"
|
|
1036
|
-
git init -q
|
|
1037
|
-
write_session "$INVALID_SESSION" "$INVALID_ROOT" "$STARTUP_DISCUSSION"
|
|
1038
|
-
|
|
1039
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$INVALID_FALLBACK" \
|
|
1040
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=any \
|
|
1041
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$INVALID_PROMPT" \
|
|
1042
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_FAILURE=invalid_output \
|
|
1043
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$INVALID_CLASSIFIER" \
|
|
1044
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_ACTION=cancel \
|
|
1045
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_PATH="$INVALID_RECOVERY" \
|
|
1046
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$INVALID_ROUTING" \
|
|
1047
|
-
pi --session "$INVALID_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$STARTUP_ROUTER_TEXT" \
|
|
1048
|
-
>"$TMPDIR/pi-cook-trigger-invalid.out" 2>"$TMPDIR/pi-cook-trigger-invalid.err"
|
|
1049
|
-
|
|
1050
|
-
python3 - "$INVALID_ROUTING" "$INVALID_CLASSIFIER" "$INVALID_FALLBACK" "$INVALID_PROMPT" "$INVALID_RECOVERY" "$TMPDIR/pi-cook-trigger-invalid.out" "$TMPDIR/pi-cook-trigger-invalid.err" <<'PY'
|
|
1051
|
-
import json
|
|
1052
|
-
import sys
|
|
1053
|
-
from pathlib import Path
|
|
1054
|
-
|
|
1055
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
1056
|
-
classifier = json.loads(Path(sys.argv[2]).read_text())
|
|
1057
|
-
fallback = Path(sys.argv[3])
|
|
1058
|
-
driver_prompt = Path(sys.argv[4])
|
|
1059
|
-
recovery = json.loads(Path(sys.argv[5]).read_text())
|
|
1060
|
-
output = Path(sys.argv[6]).read_text() + Path(sys.argv[7]).read_text()
|
|
1061
|
-
|
|
1062
|
-
assert routing['action'] == 'handled', 'invalid classifier output should stay fail-closed instead of continuing to the main agent'
|
|
1063
|
-
assert routing['reason'] == 'classifier_invalid_output_cancelled', 'invalid classifier output cancel should record the cancel outcome'
|
|
1064
|
-
assert routing['recoveryAction'] == 'cancel', 'invalid classifier output should record the cancel recovery action'
|
|
1065
|
-
assert routing['replayedToPrimaryAgent'] is False, 'invalid classifier output cancel should not replay the original message'
|
|
1066
|
-
assert classifier['result']['status'] == 'invalid_output', 'invalid classifier output should snapshot the invalid_output result'
|
|
1067
|
-
assert recovery['title'] == 'Router recovery needed before this prompt can continue', 'invalid classifier output should show the recovery chooser'
|
|
1068
|
-
assert not fallback.exists(), 'invalid classifier output cancel should keep the original input away from later fallback handlers'
|
|
1069
|
-
assert not driver_prompt.exists(), 'invalid classifier output cancel should not queue a /cook driver prompt'
|
|
1070
|
-
assert not Path('.agent').exists(), 'invalid classifier output cancel should not bootstrap canonical workflow state'
|
|
1071
|
-
assert 'rerun /cook explicitly' in output, 'invalid classifier output cancel should direct the user back to explicit /cook when needed'
|
|
1072
|
-
PY
|
|
1073
|
-
|
|
1074
|
-
# Classifier subprocess errors should also surface recovery UI and stay fail-closed on cancel.
|
|
1075
|
-
ERROR_ROOT="$TMPDIR/error-repo"
|
|
1076
|
-
ERROR_SESSION="$TMPDIR/error-session.jsonl"
|
|
1077
|
-
ERROR_ROUTING="$TMPDIR/error-routing.json"
|
|
1078
|
-
ERROR_CLASSIFIER="$TMPDIR/error-classifier.json"
|
|
1079
|
-
ERROR_FALLBACK="$TMPDIR/error-fallback.json"
|
|
1080
|
-
ERROR_PROMPT="$TMPDIR/error-driver-prompt.txt"
|
|
1081
|
-
ERROR_RECOVERY="$TMPDIR/error-recovery.json"
|
|
1082
|
-
mkdir -p "$ERROR_ROOT"
|
|
1083
|
-
cd "$ERROR_ROOT"
|
|
1084
|
-
git init -q
|
|
1085
|
-
write_session "$ERROR_SESSION" "$ERROR_ROOT" "$STARTUP_DISCUSSION"
|
|
1086
|
-
|
|
1087
|
-
PI_COOK_TRIGGER_FALLBACK_PATH="$ERROR_FALLBACK" \
|
|
1088
|
-
PI_COOK_TRIGGER_FALLBACK_SOURCE=any \
|
|
1089
|
-
PI_COMPLETION_TEST_DRIVER_PROMPT_PATH="$ERROR_PROMPT" \
|
|
1090
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_FAILURE=error \
|
|
1091
|
-
PI_COMPLETION_TEST_TRIGGER_CLASSIFIER_SNAPSHOT_PATH="$ERROR_CLASSIFIER" \
|
|
1092
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_ACTION=cancel \
|
|
1093
|
-
PI_COMPLETION_TEST_TRIGGER_RECOVERY_PATH="$ERROR_RECOVERY" \
|
|
1094
|
-
PI_COMPLETION_TEST_TRIGGER_ROUTING_PATH="$ERROR_ROUTING" \
|
|
1095
|
-
pi --session "$ERROR_SESSION" -e "$PKG_ROOT" -e "$FALLBACK_EXTENSION" -p "$STARTUP_ROUTER_TEXT" \
|
|
1096
|
-
>"$TMPDIR/pi-cook-trigger-error.out" 2>"$TMPDIR/pi-cook-trigger-error.err"
|
|
1097
|
-
|
|
1098
|
-
python3 - "$ERROR_ROUTING" "$ERROR_CLASSIFIER" "$ERROR_FALLBACK" "$ERROR_PROMPT" "$ERROR_RECOVERY" "$TMPDIR/pi-cook-trigger-error.out" "$TMPDIR/pi-cook-trigger-error.err" <<'PY'
|
|
1099
|
-
import json
|
|
1100
|
-
import sys
|
|
1101
|
-
from pathlib import Path
|
|
1102
|
-
|
|
1103
|
-
routing = json.loads(Path(sys.argv[1]).read_text())
|
|
1104
|
-
classifier = json.loads(Path(sys.argv[2]).read_text())
|
|
1105
|
-
fallback = Path(sys.argv[3])
|
|
1106
|
-
driver_prompt = Path(sys.argv[4])
|
|
1107
|
-
recovery = json.loads(Path(sys.argv[5]).read_text())
|
|
1108
|
-
output = Path(sys.argv[6]).read_text() + Path(sys.argv[7]).read_text()
|
|
1109
|
-
|
|
1110
|
-
assert routing['action'] == 'handled', 'classifier errors should stay fail-closed instead of continuing to the main agent'
|
|
1111
|
-
assert routing['reason'] == 'classifier_error_cancelled', 'classifier errors should record the cancel outcome'
|
|
1112
|
-
assert routing['recoveryAction'] == 'cancel', 'classifier errors should record the cancel recovery action'
|
|
1113
|
-
assert routing['replayedToPrimaryAgent'] is False, 'classifier error cancel should not replay the original message'
|
|
1114
|
-
assert classifier['result']['status'] == 'error', 'classifier errors should snapshot the error result'
|
|
1115
|
-
assert recovery['actions'][2]['id'] == 'cancel', 'classifier errors should expose cancel in the recovery chooser'
|
|
1116
|
-
assert not fallback.exists(), 'classifier error cancel should keep the original input away from later fallback handlers'
|
|
1117
|
-
assert not driver_prompt.exists(), 'classifier error cancel should not queue a /cook driver prompt'
|
|
1118
|
-
assert not Path('.agent').exists(), 'classifier error cancel should not bootstrap canonical workflow state'
|
|
1119
|
-
assert 'rerun /cook explicitly' in output, 'classifier error cancel should direct the user back to explicit /cook when needed'
|
|
1120
|
-
PY
|
|
1121
|
-
|
|
1122
|
-
echo "cook trigger routing test passed: $TMPDIR"
|