cuelo 0.5.2 → 0.5.4
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/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +10 -10
- package/.next/build-manifest.json +2 -2
- package/.next/prerender-manifest.json +3 -3
- package/.next/required-server-files.js +2 -2
- package/.next/required-server-files.json +2 -2
- package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/updates/route.js +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +4 -4
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +3 -3
- package/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/page.js +3 -3
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover/page_client-reference-manifest.js +1 -1
- package/.next/server/app/recover.html +1 -1
- package/.next/server/app/recover.rsc +2 -2
- package/.next/server/app/recover.segments/_full.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_head.segment.rsc +1 -1
- package/.next/server/app/recover.segments/_index.segment.rsc +2 -2
- package/.next/server/app/recover.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/recover.segments/recover.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +10 -10
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/app/{layout-5217e6f9b3d4a4b8.js → layout-61356b8386302172.js} +1 -1
- package/.next/static/chunks/app/{page-28b47a94fe58eaba.js → page-d1291e02ffa77a51.js} +3 -3
- package/.next/trace +104 -104
- package/.next/trace-build +1 -1
- package/README.md +3 -3
- package/Tools/CUELO_Setup/files/native-runtime-patch.js +5 -5
- package/Tools/OMP_Global_Config/agent/RULES.md +12 -0
- package/Tools/OMP_Global_Config/agent/agents/maker.md +11 -8
- package/Tools/OMP_Global_Config/agent/extensions/character-voice.ts +26 -45
- package/Tools/OMP_Global_Config/agent/extensions/external-advice-check.ts +138 -0
- package/Tools/OMP_Global_Config/agent/extensions/jev-runtime.ts +154 -10
- package/Tools/OMP_Global_Config/agent/extensions/lib/maker-routing.ts +24 -4
- package/Tools/OMP_Global_Config/agent/extensions/lib/routing-ledger.ts +2 -0
- package/Tools/OMP_Global_Config/agent/extensions/steering-reply-gate.ts +164 -52
- package/Tools/OMP_Global_Config/agent/extensions/todo-nudge.ts +137 -45
- package/Tools/OMP_Global_Config/agent/extensions/turn-end-guard.ts +249 -18
- package/Tools/OMP_Global_Config/agent/prompt-copies/AGENTS.en.md +1 -1
- package/Tools/OMP_Global_Config/agent/prompt-copies/RULES.en.md +2 -1
- package/Tools/OMP_Global_Config/agent/rules/harness-policy.json +78 -10
- package/Tools/OMP_Global_Config/agent/rules/subagent.md +29 -2
- package/Tools/OMP_Global_Config/agent/rules/verdict.md +10 -3
- package/Tools/OMP_Global_Config/agent/sop/_common.md +10 -7
- package/Tools/OMP_Global_Config/agent/tools/git-finalizer/index.ts +101 -2
- package/Tools/OMP_Global_Config/patches/apply-core-patch.mjs +322 -95
- package/install.mjs +1 -1
- package/package.json +9 -9
- /package/.next/static/{bGz6gPjG3dJqXPXBB1JeP → Xey7BdddcpqFxM5-ikcal}/_buildManifest.js +0 -0
- /package/.next/static/{bGz6gPjG3dJqXPXBB1JeP → Xey7BdddcpqFxM5-ikcal}/_ssgManifest.js +0 -0
|
@@ -64,15 +64,17 @@ const EDITS = [
|
|
|
64
64
|
patched: "\thasSteeringMessages?: () => boolean | SteeringQueueState | Promise<boolean | SteeringQueueState>;\n\t/** Non-consuming full queue snapshot for generation-time user steering. */\n\tpeekSteeringMessages?: () => readonly AgentMessage[];\n\t/** Notify an active response before newly queued user steering can race speculative admission. */\n\tonUserSteeringQueued?: (listener: () => void) => () => void;",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
+
// 18.3.3은 hasSteeringMessages 본문을 `steeringQueueState(...)` 호출 한 줄로 바꿨다
|
|
68
|
+
// (agent.ts:1772). 두 속성(peek·listener)은 그 속성 바로 앞에 붙는 의미 그대로다.
|
|
67
69
|
file: "../pi-agent-core/src/agent.ts",
|
|
68
70
|
marker: "onUserSteeringQueued: listener => {",
|
|
69
|
-
anchor: "\t\t\thasSteeringMessages: () =>
|
|
71
|
+
anchor: "\t\t\thasSteeringMessages: () =>",
|
|
70
72
|
patched: ` peekSteeringMessages: () => this.peekSteeringQueue(),
|
|
71
73
|
onUserSteeringQueued: listener => {
|
|
72
74
|
this.#userSteeringListeners.add(listener);
|
|
73
75
|
return () => this.#userSteeringListeners.delete(listener);
|
|
74
76
|
},
|
|
75
|
-
hasSteeringMessages: ()
|
|
77
|
+
hasSteeringMessages: () =>`,
|
|
76
78
|
},
|
|
77
79
|
{
|
|
78
80
|
// A speculative read may have physically started before the steer
|
|
@@ -2401,13 +2403,17 @@ async function loadExternalMakerRuntime(
|
|
|
2401
2403
|
patched: "\t/** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */\n\teffort?: \"lo\" | \"med\" | \"hi\";\n\t/**\n\t * Standard model selector for this spawn (flat form), e.g. \"provider/model-id:max\".\n\t * Same forwarding and fall-through semantics as the batch item field.\n\t */\n\tmodel?: string;",
|
|
2402
2404
|
},
|
|
2403
2405
|
{
|
|
2406
|
+
// 18.3.4는 모든 task schema에 필수 `solutionSpace: "string"` 을 `task` 바로 뒤에 넣었다
|
|
2407
|
+
// (types.ts:52-185). per-spawn `model?` 은 그 필수 필드 뒤에 둔다. 의미는 18.3.2와 같다.
|
|
2404
2408
|
file: "src/task/types.ts",
|
|
2405
2409
|
marker: `task: "string",
|
|
2410
|
+
solutionSpace: "string",
|
|
2406
2411
|
"model?": "string",`,
|
|
2407
2412
|
anchor: `export const taskItemSchema = type({
|
|
2408
2413
|
"name?": "string",
|
|
2409
2414
|
agent: "string = 'task'",
|
|
2410
2415
|
task: "string",
|
|
2416
|
+
solutionSpace: "string",
|
|
2411
2417
|
"outputSchema?": outputSchemaInputSchema,
|
|
2412
2418
|
"schemaMode?": '"permissive" | "strict"',
|
|
2413
2419
|
"tools?": "string[]",
|
|
@@ -2417,6 +2423,7 @@ const taskItemSchemaIsolated = type({
|
|
|
2417
2423
|
"name?": "string",
|
|
2418
2424
|
agent: "string = 'task'",
|
|
2419
2425
|
task: "string",
|
|
2426
|
+
solutionSpace: "string",
|
|
2420
2427
|
"outputSchema?": outputSchemaInputSchema,
|
|
2421
2428
|
"schemaMode?": '"permissive" | "strict"',
|
|
2422
2429
|
"tools?": "string[]",
|
|
@@ -2427,6 +2434,7 @@ const taskItemSchemaIsolated = type({
|
|
|
2427
2434
|
"name?": "string",
|
|
2428
2435
|
agent: "string = 'task'",
|
|
2429
2436
|
task: "string",
|
|
2437
|
+
solutionSpace: "string",
|
|
2430
2438
|
"model?": "string",
|
|
2431
2439
|
"outputSchema?": outputSchemaInputSchema,
|
|
2432
2440
|
"schemaMode?": '"permissive" | "strict"',
|
|
@@ -2437,6 +2445,7 @@ const taskItemSchemaIsolated = type({
|
|
|
2437
2445
|
"name?": "string",
|
|
2438
2446
|
agent: "string = 'task'",
|
|
2439
2447
|
task: "string",
|
|
2448
|
+
solutionSpace: "string",
|
|
2440
2449
|
"model?": "string",
|
|
2441
2450
|
"outputSchema?": outputSchemaInputSchema,
|
|
2442
2451
|
"schemaMode?": '"permissive" | "strict"',
|
|
@@ -2447,43 +2456,45 @@ const taskItemSchemaIsolated = type({
|
|
|
2447
2456
|
},
|
|
2448
2457
|
{
|
|
2449
2458
|
file: "src/task/types.ts",
|
|
2450
|
-
marker: `
|
|
2451
|
-
"
|
|
2452
|
-
"
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
"
|
|
2459
|
+
marker: `export const taskSchema = type({
|
|
2460
|
+
"name?": "string",
|
|
2461
|
+
agent: "string = 'task'",
|
|
2462
|
+
task: "string",
|
|
2463
|
+
solutionSpace: "string",
|
|
2464
|
+
"model?": "string",`,
|
|
2456
2465
|
anchor: `export const taskSchema = type({
|
|
2457
2466
|
"name?": "string",
|
|
2458
2467
|
agent: "string = 'task'",
|
|
2459
2468
|
task: "string",
|
|
2469
|
+
solutionSpace: "string",
|
|
2460
2470
|
"outputSchema?": outputSchemaInputSchema,`,
|
|
2461
2471
|
patched: `export const taskSchema = type({
|
|
2462
2472
|
"name?": "string",
|
|
2463
2473
|
agent: "string = 'task'",
|
|
2464
2474
|
task: "string",
|
|
2475
|
+
solutionSpace: "string",
|
|
2465
2476
|
"model?": "string",
|
|
2466
2477
|
"outputSchema?": outputSchemaInputSchema,`,
|
|
2467
2478
|
},
|
|
2468
2479
|
{
|
|
2469
2480
|
file: "src/task/types.ts",
|
|
2470
|
-
marker: `
|
|
2471
|
-
"
|
|
2472
|
-
"
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
"
|
|
2476
|
-
});
|
|
2477
|
-
const taskSchemaBatch = type({`,
|
|
2481
|
+
marker: `const taskSchemaNoIsolation = type({
|
|
2482
|
+
"name?": "string",
|
|
2483
|
+
agent: "string = 'task'",
|
|
2484
|
+
task: "string",
|
|
2485
|
+
solutionSpace: "string",
|
|
2486
|
+
"model?": "string",`,
|
|
2478
2487
|
anchor: `const taskSchemaNoIsolation = type({
|
|
2479
2488
|
"name?": "string",
|
|
2480
2489
|
agent: "string = 'task'",
|
|
2481
2490
|
task: "string",
|
|
2491
|
+
solutionSpace: "string",
|
|
2482
2492
|
"outputSchema?": outputSchemaInputSchema,`,
|
|
2483
2493
|
patched: `const taskSchemaNoIsolation = type({
|
|
2484
2494
|
"name?": "string",
|
|
2485
2495
|
agent: "string = 'task'",
|
|
2486
2496
|
task: "string",
|
|
2497
|
+
solutionSpace: "string",
|
|
2487
2498
|
"model?": "string",
|
|
2488
2499
|
"outputSchema?": outputSchemaInputSchema,`,
|
|
2489
2500
|
},
|
|
@@ -2502,6 +2513,7 @@ const taskSchemaBatch = type({`,
|
|
|
2502
2513
|
"name?": "string",
|
|
2503
2514
|
agent,
|
|
2504
2515
|
task: "string",
|
|
2516
|
+
solutionSpace: "string",
|
|
2505
2517
|
...effortField,
|
|
2506
2518
|
"outputSchema?": outputSchemaInputSchema,
|
|
2507
2519
|
"schemaMode?": '"permissive" | "strict"',
|
|
@@ -2514,6 +2526,7 @@ const taskSchemaBatch = type({`,
|
|
|
2514
2526
|
"name?": "string",
|
|
2515
2527
|
agent,
|
|
2516
2528
|
task: "string",
|
|
2529
|
+
solutionSpace: "string",
|
|
2517
2530
|
"model?": "string",
|
|
2518
2531
|
...effortField,
|
|
2519
2532
|
"outputSchema?": outputSchemaInputSchema,
|
|
@@ -2536,6 +2549,7 @@ const taskSchemaBatch = type({`,
|
|
|
2536
2549
|
"name?": "string",
|
|
2537
2550
|
agent,
|
|
2538
2551
|
task: "string",
|
|
2552
|
+
solutionSpace: "string",
|
|
2539
2553
|
...effortField,
|
|
2540
2554
|
"outputSchema?": outputSchemaInputSchema,
|
|
2541
2555
|
"schemaMode?": '"permissive" | "strict"',
|
|
@@ -2546,6 +2560,7 @@ const taskSchemaBatch = type({`,
|
|
|
2546
2560
|
"name?": "string",
|
|
2547
2561
|
agent,
|
|
2548
2562
|
task: "string",
|
|
2563
|
+
solutionSpace: "string",
|
|
2549
2564
|
"model?": "string",
|
|
2550
2565
|
...effortField,
|
|
2551
2566
|
"outputSchema?": outputSchemaInputSchema,
|
|
@@ -2582,12 +2597,42 @@ const taskSchemaBatch = type({`,
|
|
|
2582
2597
|
file: "src/task/index.ts",
|
|
2583
2598
|
marker: `...(params.model !== undefined ? { model: params.model } : {}),
|
|
2584
2599
|
...(params.tools?.length`,
|
|
2600
|
+
// 18.3.4는 effort 다음 줄에 `solutionSpace: params.solutionSpace,` 를 넣었다(index.ts:1506).
|
|
2585
2601
|
anchor: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
|
|
2602
|
+
solutionSpace: params.solutionSpace,
|
|
2586
2603
|
...(params.tools?.length`,
|
|
2587
2604
|
patched: ` ...(params.effort !== undefined ? { effort: params.effort } : {}),
|
|
2605
|
+
solutionSpace: params.solutionSpace,
|
|
2588
2606
|
...(params.model !== undefined ? { model: params.model } : {}),
|
|
2589
2607
|
...(params.tools?.length`,
|
|
2590
2608
|
},
|
|
2609
|
+
{
|
|
2610
|
+
// 18.3.3 BLOCK: upstream 은 `task`·`bash` 를 가진 SubAgent 정의에 `wait` 를 자동으로 더한다
|
|
2611
|
+
// (executor.ts:3505-3516). CUELO 계약은 Main 전용 wait 다(harness-policy
|
|
2612
|
+
// `mainLane.waitContract.barrierCall`: subagents have no wait tool, 자기 job 결과는 자동으로
|
|
2613
|
+
// 다시 깨운다). Maker 정의는 bash 를 가지므로 이 확장이 곧 모든 Maker 에 wait 를 준다
|
|
2614
|
+
// (격리 18.3.4 probe: maker[read,bash] → [read,bash,wait,yield]). 자동 확장만 막고, agent
|
|
2615
|
+
// 정의가 `wait` 를 직접 적은 경우(upstream 의 "explicitly requested" 경로)는 그대로 받는다.
|
|
2616
|
+
file: "src/task/executor.ts",
|
|
2617
|
+
marker: "// HANSE: wait stays Main-only; a subagent gets it only from its own definition.",
|
|
2618
|
+
anchor: ` // Agents that can start background work (\`task\`, \`bash\`) need \`wait\` to block on it;
|
|
2619
|
+
// without it they \`sleep\`. Runs after \`exec\` expansion and the max-depth \`task\` strip.
|
|
2620
|
+
// \`createTools\` still drops it when no wake source (async/IRC/services) is enabled.
|
|
2621
|
+
// Restricted sessions own their explicit list and are never widened.
|
|
2622
|
+
if (
|
|
2623
|
+
toolNames &&
|
|
2624
|
+
!options.restrictToolNames &&
|
|
2625
|
+
!toolNames.includes("wait") &&
|
|
2626
|
+
(toolNames.includes("task") || toolNames.includes("bash"))
|
|
2627
|
+
) {
|
|
2628
|
+
toolNames = [...toolNames, "wait"];
|
|
2629
|
+
}
|
|
2630
|
+
`,
|
|
2631
|
+
patched: ` // HANSE: wait stays Main-only; a subagent gets it only from its own definition.
|
|
2632
|
+
// Upstream widened every task/bash subagent here; CUELO subagents are re-woken by
|
|
2633
|
+
// their own job results instead of blocking on \`wait\`.
|
|
2634
|
+
`,
|
|
2635
|
+
},
|
|
2591
2636
|
// --- BAI socket/stream-read fallback 회귀 수리 (ArenaInterface 제안 inline, proposal 파일로 분리하지 않음) ---
|
|
2592
2637
|
// 범위: unexpected socket close / stream-read 이송 오류만 같은 모델 재시도 우선. 그 외 network 오류·
|
|
2593
2638
|
// UsageLimit·타 provider·abort·credential·unsafe replay 의미는 기존 그대로. 새 retry loop·설정·전역 gate 없음.
|
|
@@ -4565,60 +4610,14 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4565
4610
|
: rawOutput;
|
|
4566
4611
|
}`,
|
|
4567
4612
|
},
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
},
|
|
4577
|
-
{
|
|
4578
|
-
// 2026-09-25 사용자 결정: 문맥이 작은데 thinking만 하다 `length`로 끊기면 upstream은
|
|
4579
|
-
// recovery compaction을 돌려 그 사고를 통째로 버린다(mega-six Markdown Maker: 59K 문맥,
|
|
4580
|
-
// 요약 "No prior history" 뒤 421초 재사고). 입력 문맥이 창의 절반 미만이면 compaction 대신
|
|
4581
|
-
// 죽은 턴만 버리고 짧게 결론 내라는 developer 안내를 넣어 이어 간다. 기존 재시도 상한을 공유한다.
|
|
4582
|
-
file: "src/session/session-maintenance.ts",
|
|
4583
|
-
marker: "source: \"hanse-length-nudge\"",
|
|
4584
|
-
anchor: "\t\t\tconst promoted = await this.#tryContextPromotion(assistantMessage);\n\t\t\tif (promoted) {\n\t\t\t\tawait this.#host.dropPersistedAssistantTurn(assistantMessage);\n\t\t\t\tthis.#incompleteRecoveryAttempts = 0;\n\t\t\t\tlogger.debug(\"Context promotion triggered by response.incomplete (length stop)\", {",
|
|
4585
|
-
patched: ` const hanseUsage = assistantMessage.usage;
|
|
4586
|
-
const hanseInputTokens = (hanseUsage?.input ?? 0) + (hanseUsage?.cacheRead ?? 0) + (hanseUsage?.cacheWrite ?? 0);
|
|
4587
|
-
const hanseWindow = this.#host.model()?.contextWindow ?? 0;
|
|
4588
|
-
const hanseReasoningOnly = !assistantMessage.content.some(
|
|
4589
|
-
c => c.type === "toolCall" || (c.type === "text" && c.text.trim().length > 0),
|
|
4590
|
-
);
|
|
4591
|
-
if (
|
|
4592
|
-
hanseReasoningOnly &&
|
|
4593
|
-
hanseWindow > 0 &&
|
|
4594
|
-
hanseInputTokens < hanseWindow * 0.5 &&
|
|
4595
|
-
this.#incompleteRecoveryAttempts < INCOMPLETE_RECOVERY_MAX_RETRIES
|
|
4596
|
-
) {
|
|
4597
|
-
this.#incompleteRecoveryAttempts++;
|
|
4598
|
-
await this.#host.dropPersistedAssistantTurn(assistantMessage);
|
|
4599
|
-
this.#host.agent.appendMessage({
|
|
4600
|
-
role: "developer",
|
|
4601
|
-
content: [
|
|
4602
|
-
{
|
|
4603
|
-
type: "text",
|
|
4604
|
-
text: "Your previous response hit the output token limit while still reasoning and produced no text or tool call; that reasoning was lost. Do not re-derive the whole design. Reason briefly, then immediately make the next concrete tool call (write/edit the code in small steps).",
|
|
4605
|
-
},
|
|
4606
|
-
],
|
|
4607
|
-
attribution: "agent",
|
|
4608
|
-
timestamp: Date.now(),
|
|
4609
|
-
synthetic: true,
|
|
4610
|
-
});
|
|
4611
|
-
logger.debug("HANSE length nudge instead of compaction", { inputTokens: hanseInputTokens, window: hanseWindow });
|
|
4612
|
-
this.#host.scheduleAgentContinue({ source: "hanse-length-nudge", delayMs: 100, generation });
|
|
4613
|
-
return COMPACTION_CHECK_CONTINUATION;
|
|
4614
|
-
}
|
|
4615
|
-
|
|
4616
|
-
const promoted = await this.#tryContextPromotion(assistantMessage);
|
|
4617
|
-
if (promoted) {
|
|
4618
|
-
await this.#host.dropPersistedAssistantTurn(assistantMessage);
|
|
4619
|
-
this.#incompleteRecoveryAttempts = 0;
|
|
4620
|
-
logger.debug("Context promotion triggered by response.incomplete (length stop)", {`,
|
|
4621
|
-
},
|
|
4613
|
+
// 18.3.4 RETIRE: OAuth 64K 출력 상한 해제(옛 2026-09-25 항목). pi-ai 18.3.4 anthropic.ts:4743 이
|
|
4614
|
+
// OAuth·API key 모두 `model.maxTokens ?? 64_000` 을 요청한다(CHANGELOG 18.3.4 "Fixed Anthropic OAuth
|
|
4615
|
+
// requests capping output at 64k tokens"; Opus 5.5 128k).
|
|
4616
|
+
// 18.3.4 RETIRE: reasoning-only `length` 뒤 compaction 대신 짧게 이어 가라는 nudge(옛 hanse-length-nudge).
|
|
4617
|
+
// session-maintenance.ts:3104-3134 가 창 여유(compaction 임계 90% 미만)에서 죽은 턴만 버리고
|
|
4618
|
+
// `length-stop-retry.md` 안내를 넣어 재시도하며, 상한 도달 시 3083-3101 이 retainTerminalFailure 로
|
|
4619
|
+
// 실패를 남긴다. 옛 조건(입력 < 창 50%)의 상위집합이다. 유일한 차이였던 context promotion 선행은
|
|
4620
|
+
// `contextPromotion.enabled` 기본 false·CUELO 미설정이라 동작 차이가 없다.
|
|
4622
4621
|
{
|
|
4623
4622
|
// 2026-09-25: steering-reply gate가 스트리밍 이벤트 순서에 기대면, 도구가 미리 실행될 때 같은 응답의
|
|
4624
4623
|
// 앞선 답 텍스트를 보지 못해 잘못 막는다. 판정 대상 assistant 메시지를 tool_call 이벤트에 싣는다.
|
|
@@ -4627,23 +4626,35 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4627
4626
|
anchor: "\t\t\t\ttype: \"tool_call\",\n\t\t\t\ttoolName: ctx.tool.name,\n\t\t\t\ttoolCallId: ctx.toolCall.id,\n",
|
|
4628
4627
|
patched: "\t\t\t\ttype: \"tool_call\",\n\t\t\t\ttoolName: ctx.tool.name,\n\t\t\t\ttoolCallId: ctx.toolCall.id,\n\t\t\t\tassistantMessage: ctx.assistantMessage, // HANSE: steering gate\n",
|
|
4629
4628
|
},
|
|
4630
|
-
// 2026-09-
|
|
4631
|
-
// omitThinking(`display: "omitted"`)은 그 문장까지
|
|
4632
|
-
//
|
|
4633
|
-
//
|
|
4629
|
+
// 2026-09-27: Opus 5.5는 도구 앞 사용자용 문장(progress update)을 별도 서명의 thinking 블록으로 보낸다.
|
|
4630
|
+
// omitThinking(`display: "omitted"`)은 그 문장까지 비운다(2026-09-26 세션 79개 중 25개 메시지의 답 소실).
|
|
4631
|
+
// 공식 `display: "updates"`(beta)는 reasoning은 비운 채 progress update만 텍스트로 보내므로, 공식 Anthropic API의
|
|
4632
|
+
// omitThinking 요청은 그 값으로 보내고 텍스트가 온 thinking을 본문 text 사본으로 보인다. 서명은 불투명 값이라
|
|
4633
|
+
// 해석하지 않는다. 다시 보낼 때는 서명된 thinking을 원래대로 보내고 화면용 사본은 뺀다.
|
|
4634
4634
|
{
|
|
4635
4635
|
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4636
|
-
marker: "function
|
|
4636
|
+
marker: "function isHanseThinkingUpdatesRequest(",
|
|
4637
4637
|
anchor: "function unwrapAnthropicThinkingEnvelope(text: string): string | undefined {\n",
|
|
4638
4638
|
patched: [
|
|
4639
|
-
"/** HANSE:
|
|
4640
|
-
"
|
|
4641
|
-
"
|
|
4642
|
-
"
|
|
4643
|
-
"
|
|
4644
|
-
"\
|
|
4645
|
-
"\
|
|
4646
|
-
"\
|
|
4639
|
+
"/** HANSE: `thinking.display: \"updates\"`는 이 beta 헤더와 함께 보내야 받는다. */",
|
|
4640
|
+
"const HANSE_THINKING_DISPLAY_UPDATES_BETA = \"thinking-display-updates-2026-08-18\";",
|
|
4641
|
+
"",
|
|
4642
|
+
"/** HANSE: omitThinking 요청을 공식 progress-update 경로로 보낼지. 요청(buildParams)·beta·스트림이 같은 판정을 쓴다. */",
|
|
4643
|
+
"function isHanseThinkingUpdatesRequest(",
|
|
4644
|
+
"\tmodel: Model<\"anthropic-messages\">,",
|
|
4645
|
+
"\toptions: AnthropicOptions | undefined,",
|
|
4646
|
+
"\tbaseUrl: string | undefined,",
|
|
4647
|
+
"): boolean {",
|
|
4648
|
+
"\treturn (",
|
|
4649
|
+
"\t\toptions?.thinkingDisplay === \"omitted\" &&",
|
|
4650
|
+
"\t\toptions.client === undefined &&",
|
|
4651
|
+
"\t\tmodel.reasoning &&",
|
|
4652
|
+
"\t\t(options.thinkingEnabled === true || model.compat.requiresThinkingEnabled === true) &&",
|
|
4653
|
+
"\t\tmodel.thinking?.mode === \"anthropic-adaptive\" &&",
|
|
4654
|
+
"\t\t!model.compat.disableAdaptiveThinking &&",
|
|
4655
|
+
"\t\tmodel.thinking.supportsDisplay === true &&",
|
|
4656
|
+
"\t\tisOfficialAnthropicApiUrl(baseUrl)",
|
|
4657
|
+
"\t);",
|
|
4647
4658
|
"}",
|
|
4648
4659
|
"",
|
|
4649
4660
|
"function unwrapAnthropicThinkingEnvelope(text: string): string | undefined {",
|
|
@@ -4652,11 +4663,11 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4652
4663
|
},
|
|
4653
4664
|
{
|
|
4654
4665
|
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4655
|
-
marker: "const
|
|
4666
|
+
marker: "const hanseThinkingUpdates = isHanseThinkingUpdatesRequest(model, options, baseUrl);",
|
|
4656
4667
|
anchor: "\t\t\tconst finalizeStreamBlock = (block: Block, contentIndex: number): void => {\n",
|
|
4657
4668
|
patched: [
|
|
4658
|
-
"\t\t\t// HANSE:
|
|
4659
|
-
"\t\t\tconst
|
|
4669
|
+
"\t\t\t// HANSE: updates로 보낸 요청에서 텍스트가 온 thinking은 progress update다. 그 문장만 본문으로 보인다.",
|
|
4670
|
+
"\t\t\tconst hanseThinkingUpdates = isHanseThinkingUpdatesRequest(model, options, baseUrl);",
|
|
4660
4671
|
"\t\t\tconst finalizeStreamBlock = (block: Block, contentIndex: number): void => {",
|
|
4661
4672
|
"",
|
|
4662
4673
|
].join("\n"),
|
|
@@ -4667,8 +4678,8 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4667
4678
|
anchor: "\t\t\t\t\t\t\t\tstreamedReplayUnsafeContent = true;\n\t\t\t\t\t\t\t\tblock.thinking += event.delta.thinking;\n",
|
|
4668
4679
|
patched: [
|
|
4669
4680
|
"\t\t\t\t\t\t\t\tstreamedReplayUnsafeContent = true;",
|
|
4670
|
-
"\t\t\t\t\t\t\t\tif (
|
|
4671
|
-
"\t\t\t\t\t\t\t\t\t//
|
|
4681
|
+
"\t\t\t\t\t\t\t\tif (hanseThinkingUpdates) {",
|
|
4682
|
+
"\t\t\t\t\t\t\t\t\t// 서명된 블록은 비워 둔 채 보내고, 모은 문장은 블록 끝에서 본문 사본으로 내보낸다.",
|
|
4672
4683
|
"\t\t\t\t\t\t\t\t\tconst hanseBlock = block as Block & { hanseHiddenThinking?: string };",
|
|
4673
4684
|
"\t\t\t\t\t\t\t\t\thanseBlock.hanseHiddenThinking = (hanseBlock.hanseHiddenThinking ?? \"\") + event.delta.thinking;",
|
|
4674
4685
|
"\t\t\t\t\t\t\t\t\tcontinue;",
|
|
@@ -4683,11 +4694,11 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4683
4694
|
anchor: "\t\t\t\t\tstream.push({ type: \"thinking_end\", contentIndex, content: block.thinking, partial: output });\n",
|
|
4684
4695
|
patched: [
|
|
4685
4696
|
"\t\t\t\t\tstream.push({ type: \"thinking_end\", contentIndex, content: block.thinking, partial: output });",
|
|
4686
|
-
"\t\t\t\t\tif (
|
|
4697
|
+
"\t\t\t\t\tif (hanseThinkingUpdates) {",
|
|
4687
4698
|
"\t\t\t\t\t\tconst hanseBlock = block as Block & { hanseHiddenThinking?: string };",
|
|
4688
4699
|
"\t\t\t\t\t\tconst hanseHidden = (hanseBlock.hanseHiddenThinking ?? \"\").trim();",
|
|
4689
4700
|
"\t\t\t\t\t\tdelete hanseBlock.hanseHiddenThinking;",
|
|
4690
|
-
"\t\t\t\t\t\tif (hanseHidden
|
|
4701
|
+
"\t\t\t\t\t\tif (hanseHidden) {",
|
|
4691
4702
|
"\t\t\t\t\t\t\tconst narration = { type: \"text\", text: hanseHidden, hanseNarration: true, [kStreamingBlockIndex]: -1 } as unknown as Block;",
|
|
4692
4703
|
"\t\t\t\t\t\t\toutput.content.push(narration as AssistantMessage[\"content\"][number]);",
|
|
4693
4704
|
"\t\t\t\t\t\t\tconst narrationIndex = output.content.length - 1;",
|
|
@@ -4701,16 +4712,40 @@ import { getActiveRules } from "../capability/rule";`,
|
|
|
4701
4712
|
},
|
|
4702
4713
|
{
|
|
4703
4714
|
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4704
|
-
marker: "
|
|
4715
|
+
marker: "display?: AnthropicThinkingDisplay | \"updates\" } = { type: \"adaptive\" }; // HANSE",
|
|
4716
|
+
anchor: "const adaptive: { type: \"adaptive\"; display?: AnthropicThinkingDisplay } = { type: \"adaptive\" };",
|
|
4717
|
+
patched: "const adaptive: { type: \"adaptive\"; display?: AnthropicThinkingDisplay | \"updates\" } = { type: \"adaptive\" }; // HANSE: progress-update display",
|
|
4718
|
+
},
|
|
4719
|
+
{
|
|
4720
|
+
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4721
|
+
marker: "? \"updates\" : (thinkingOptions.thinkingDisplay ?? \"summarized\"); // HANSE",
|
|
4705
4722
|
anchor: "adaptive.display = thinkingOptions.thinkingDisplay ?? \"summarized\";",
|
|
4706
|
-
patched: "adaptive.display = \"summarized\"; // HANSE:
|
|
4723
|
+
patched: "adaptive.display = isHanseThinkingUpdatesRequest(model, options, effectiveBaseUrl ?? model.baseUrl) ? \"updates\" : (thinkingOptions.thinkingDisplay ?? \"summarized\"); // HANSE: omitThinking은 공식 progress-update 경로",
|
|
4707
4724
|
},
|
|
4708
4725
|
{
|
|
4726
|
+
// 위 display와 짝: beta가 없으면 updates 요청은 거부된다. fallback-credit 재구성이 extraBetas를 비우므로 그 뒤에 붙인다.
|
|
4709
4727
|
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4710
|
-
marker: "
|
|
4711
|
-
anchor: "
|
|
4712
|
-
patched:
|
|
4728
|
+
marker: "extraBetas.push(HANSE_THINKING_DISPLAY_UPDATES_BETA);",
|
|
4729
|
+
anchor: "\t\t\t\tclientArgs = {\n\t\t\t\t\tmodel,\n\t\t\t\t\tapiKey,\n\t\t\t\t\textraBetas,\n",
|
|
4730
|
+
patched: [
|
|
4731
|
+
"\t\t\t\tif (isHanseThinkingUpdatesRequest(model, options, baseUrl) && !extraBetas.includes(HANSE_THINKING_DISPLAY_UPDATES_BETA)) {",
|
|
4732
|
+
"\t\t\t\t\textraBetas.push(HANSE_THINKING_DISPLAY_UPDATES_BETA);",
|
|
4733
|
+
"\t\t\t\t}",
|
|
4734
|
+
"\t\t\t\tclientArgs = {",
|
|
4735
|
+
"\t\t\t\t\tmodel,",
|
|
4736
|
+
"\t\t\t\t\tapiKey,",
|
|
4737
|
+
"\t\t\t\t\textraBetas,",
|
|
4738
|
+
"",
|
|
4739
|
+
].join("\n"),
|
|
4740
|
+
},
|
|
4741
|
+
{
|
|
4742
|
+
file: "../pi-ai/src/providers/anthropic-wire.ts",
|
|
4743
|
+
marker: "display?: \"summarized\" | \"omitted\" | \"updates\"; // HANSE",
|
|
4744
|
+
anchor: "export type ThinkingConfigAdaptive = {\n\ttype: \"adaptive\";\n\t/** Opus 4.7+ reasoning display mode. */\n\tdisplay?: \"summarized\" | \"omitted\";\n",
|
|
4745
|
+
patched: "export type ThinkingConfigAdaptive = {\n\ttype: \"adaptive\";\n\t/** Opus 4.7+ reasoning display mode. */\n\tdisplay?: \"summarized\" | \"omitted\" | \"updates\"; // HANSE: progress-update display(beta)\n",
|
|
4713
4746
|
},
|
|
4747
|
+
// 2026-09-27 RETIRE: budget(`type: "enabled"`) 경로의 summarized 강제. progress update는 adaptive 모델에만 있고
|
|
4748
|
+
// 서명 판정을 없앴으므로, omitThinking에서 숨길 요약을 받아 올 이유가 없다(upstream 값을 그대로 쓴다).
|
|
4714
4749
|
{
|
|
4715
4750
|
file: "../pi-ai/src/providers/anthropic.ts",
|
|
4716
4751
|
marker: "\"hanseNarration\" in block && block.hanseNarration === true && hasSignedThinking",
|
|
@@ -4817,6 +4852,198 @@ export function hostNextServerEnvUnsets(
|
|
|
4817
4852
|
anchor: "\t\tcontent: [{ type: \"text\", text: errorMessage ? `${message}: ${errorMessage}` : `${message}.` }],\n",
|
|
4818
4853
|
patched: "\t\t// HANSE: skipped reason stands alone\n\t\tcontent: [{ type: \"text\", text: reason === \"skipped\" && errorMessage ? errorMessage : errorMessage ? `${message}: ${errorMessage}` : `${message}.` }],\n",
|
|
4819
4854
|
},
|
|
4855
|
+
{
|
|
4856
|
+
// 2026-09-25·27 실측: edit 거부 안내(seen-line guard)는 native 가 만든 영어 문장이라, Main 이 바로 다음
|
|
4857
|
+
// 진행 문장을 영어로 이어 썼다. 문구는 pi-natives 바이너리 안에 있어 직접 못 고치지만 모델에는 이 파일의
|
|
4858
|
+
// 오류 반환을 거쳐 간다. 알려진 문장 틀만 한국어로 바꾸고, 틀이 다르면 원문을 그대로 둔다.
|
|
4859
|
+
file: "src/edit/index.ts",
|
|
4860
|
+
marker: "// HANSE: localized seen-line rejection",
|
|
4861
|
+
anchor: "function operationFromNative(op: string): Operation | undefined {\n",
|
|
4862
|
+
patched: `// HANSE: localized seen-line rejection
|
|
4863
|
+
const SEEN_LINE_HEAD =
|
|
4864
|
+
/^This edit anchors to lines (.+?) of (.+?) that (\\[[^\\]\\n]+\\]) never displayed \\(it showed a partial range, a search hit, or a folded summary\\)\\. (?:Actual file content at those lines|Preview of the actual file content at the first (\\d+) unseen line\\(s\\)):$/gm;
|
|
4865
|
+
const SEEN_LINE_TAIL =
|
|
4866
|
+
/^Verify the content matches what you intend to touch, then re-issue the edit with the same \\[path#tag\\] header — a straight retry now succeeds without a re-read\\. If the content does NOT match, fix your line numbers\\.$/gm;
|
|
4867
|
+
export function localizeSeenLineRejection(text: string): string {
|
|
4868
|
+
return text
|
|
4869
|
+
.replace(
|
|
4870
|
+
SEEN_LINE_HEAD,
|
|
4871
|
+
(_match, lines: string, file: string, tag: string, preview: string | undefined) =>
|
|
4872
|
+
"이 edit는 " + file + "의 " + lines + "번 줄을 기준으로 했지만, " + tag +
|
|
4873
|
+
" 스냅샷은 그 줄을 온전히 보여 준 적이 없다(부분 범위·검색 결과·접힌 요약만 보였다). " +
|
|
4874
|
+
(preview ? "보지 않은 줄 중 앞쪽 " + preview + "줄의 실제 내용:" : "그 줄의 실제 내용:"),
|
|
4875
|
+
)
|
|
4876
|
+
.replace(
|
|
4877
|
+
SEEN_LINE_TAIL,
|
|
4878
|
+
"건드리려던 내용과 맞는지 확인한 뒤 같은 [path#tag] 헤더로 edit를 다시 보낸다. 다시 읽지 않고 그대로 재시도해도 이제 성공한다. 내용이 다르면 줄 번호를 고친다.",
|
|
4879
|
+
);
|
|
4880
|
+
}
|
|
4881
|
+
|
|
4882
|
+
function operationFromNative(op: string): Operation | undefined {
|
|
4883
|
+
`,
|
|
4884
|
+
},
|
|
4885
|
+
{
|
|
4886
|
+
file: "src/edit/index.ts",
|
|
4887
|
+
marker: "text: localizeSeenLineRejection(outcome.text) }], isError: true };",
|
|
4888
|
+
anchor: "\t\t\treturn { content: [{ type: \"text\", text: outcome.text }], isError: true };\n",
|
|
4889
|
+
patched: "\t\t\treturn { content: [{ type: \"text\", text: localizeSeenLineRejection(outcome.text) }], isError: true };\n",
|
|
4890
|
+
},
|
|
4891
|
+
{
|
|
4892
|
+
// 2026-09-27 자가학습 점검: Maker(taskDepth>0) 세션은 부모 state 의 alias 로 만들어지면서
|
|
4893
|
+
// hasRecalledForFirstTurn=true 로 시작해, 자기 작업 brief 로는 한 번도 회상하지 않았다(작업 직전
|
|
4894
|
+
// 교훈 적용 연결이 끊김). 첫 턴 회상 경로(beforeAgentStartPrompt)는 child 자기 state 를 쓰고, alias 는
|
|
4895
|
+
// 부모와 같은 scoped 저장소를 공유하므로 이 값만 풀면 작업 brief 로 한 번 회상한다. 쓰기(retain)는
|
|
4896
|
+
// 기존대로 alias 에서 막힌다.
|
|
4897
|
+
file: "src/mnemopi/backend.ts",
|
|
4898
|
+
marker: "// HANSE: child recalls its own task",
|
|
4899
|
+
anchor: "\t\t\t\t\taliasOf: parent,\n\t\t\t\t\thasRecalledForFirstTurn: true,\n",
|
|
4900
|
+
patched: "\t\t\t\t\taliasOf: parent,\n\t\t\t\t\t// HANSE: child recalls its own task\n\t\t\t\t\thasRecalledForFirstTurn: false,\n",
|
|
4901
|
+
},
|
|
4902
|
+
{
|
|
4903
|
+
// 위 항목의 짝. child 는 부모의 첫 턴 회상(부모 첫 요청 기준)이 아니라 자기 작업 회상만 싣는다.
|
|
4904
|
+
// 부모 snippet 을 계속 붙이면 child 첫 요청에 서로 다른 <memories> 두 벌이 실린다.
|
|
4905
|
+
file: "src/mnemopi/backend.ts",
|
|
4906
|
+
marker: "const recallSnippet = state?.aliasOf ? state.lastRecallSnippet : primary?.lastRecallSnippet;",
|
|
4907
|
+
anchor: "\t\tif (primary?.lastRecallSnippet) parts.push(primary.lastRecallSnippet);\n",
|
|
4908
|
+
patched: "\t\tconst recallSnippet = state?.aliasOf ? state.lastRecallSnippet : primary?.lastRecallSnippet;\n\t\tif (recallSnippet) parts.push(recallSnippet);\n",
|
|
4909
|
+
},
|
|
4910
|
+
{
|
|
4911
|
+
// 주입되는 <memories> 줄에 기억 id 를 싣는다. recall 도구 결과에는 id 가 있지만 자동 주입 블록에는
|
|
4912
|
+
// 없어서, 작업에 전달된 교훈을 routing_verdict appliedLessons 로 가리킬 수 없었다.
|
|
4913
|
+
file: "src/mnemopi/state.ts",
|
|
4914
|
+
marker: "// HANSE: recall line carries memory id",
|
|
4915
|
+
anchor: "\t\treturn `- ${content}${source}${date}`;\n",
|
|
4916
|
+
patched: "\t\t// HANSE: recall line carries memory id\n\t\tconst memoryId = result.id ? ` (id: ${result.id})` : \"\";\n\t\treturn `- ${content}${source}${date}${memoryId}`;\n",
|
|
4917
|
+
},
|
|
4918
|
+
{
|
|
4919
|
+
// learn 은 rememberScoped 가 돌려준 기억 id 를 버리고 "Lesson stored." 만 알렸다. 교훈을 뒤에서
|
|
4920
|
+
// 가리킬 식별자가 없으면 적용·결과를 연결할 수 없다. mnemopi 경로에서만 id 를 싣는다.
|
|
4921
|
+
// 18.3.3은 호출을 `rememberScoped(memory, {...}, target)` 3인자로 펼쳤다(learn.ts:110-134,
|
|
4922
|
+
// global scope 지원). 반환값(기억 id)은 그대로 string 이다.
|
|
4923
|
+
file: "src/tools/learn.ts",
|
|
4924
|
+
marker: "// HANSE: learn reports memory id",
|
|
4925
|
+
anchor: "\t\t\ttry {\n\t\t\t\tstate.rememberScoped(\n",
|
|
4926
|
+
patched: "\t\t\ttry {\n\t\t\t\t// HANSE: learn reports memory id\n\t\t\t\tconst memoryId = state.rememberScoped(\n",
|
|
4927
|
+
},
|
|
4928
|
+
{
|
|
4929
|
+
file: "src/tools/learn.ts",
|
|
4930
|
+
marker: "memoryMessage = `Lesson stored (id: ${memoryId})`;",
|
|
4931
|
+
anchor: "\t\t\t\t\ttarget,\n\t\t\t\t);\n\t\t\t} catch (error) {\n",
|
|
4932
|
+
patched: "\t\t\t\t\ttarget,\n\t\t\t\t);\n\t\t\t\tif (memoryId) memoryMessage = `Lesson stored (id: ${memoryId})`;\n\t\t\t} catch (error) {\n",
|
|
4933
|
+
},
|
|
4934
|
+
{
|
|
4935
|
+
// 2026-09-27: 자동 주입 <memories> 는 세션 파일에 남지 않아, Main 단독 세션에서 어떤 교훈이 전달됐는지
|
|
4936
|
+
// 셀 수 없었다(위임 attempt 만 routing_verdict appliedLessons 로 연결됨). 첫 턴 회상이 확정될 때 전달한
|
|
4937
|
+
// 기억 id 를 LLM context 에 들어가지 않는 custom entry 로 남긴다. 소비자는 evals/analyze-lesson-recurrence.mjs.
|
|
4938
|
+
file: "src/mnemopi/state.ts",
|
|
4939
|
+
marker: "// HANSE: first-turn recall delivery is recorded",
|
|
4940
|
+
anchor: "\t\t\t\tthis.hasRecalledForFirstTurn = true;\n\t\t\t\tif (context) this.lastRecallSnippet = context;\n",
|
|
4941
|
+
patched: "\t\t\t\tthis.hasRecalledForFirstTurn = true;\n\t\t\t\tif (context) this.lastRecallSnippet = context;\n\t\t\t\t// HANSE: first-turn recall delivery is recorded\n\t\t\t\tconst deliveredIds = [...(context ?? \"\").matchAll(/\\(id: ([^)\\s]+)\\)/g)].map(match => match[1]);\n\t\t\t\tif (deliveredIds.length > 0) this.session.sessionManager.appendCustomEntry(\"mnemopi-recall\", { ids: deliveredIds });\n",
|
|
4942
|
+
},
|
|
4943
|
+
{
|
|
4944
|
+
// 위 항목의 짝. 백그라운드 첫 턴 회상(maybeRecallOnAgentStart)도 같은 기록을 남긴다.
|
|
4945
|
+
file: "src/mnemopi/state.ts",
|
|
4946
|
+
marker: "// HANSE: background recall delivery is recorded",
|
|
4947
|
+
anchor: "\t\tif (!context) return;\n\t\tthis.lastRecallSnippet = context;\n",
|
|
4948
|
+
patched: "\t\tif (!context) return;\n\t\tthis.lastRecallSnippet = context;\n\t\t// HANSE: background recall delivery is recorded\n\t\tconst deliveredIds = [...context.matchAll(/\\(id: ([^)\\s]+)\\)/g)].map(match => match[1]);\n\t\tif (deliveredIds.length > 0) this.session.sessionManager.appendCustomEntry(\"mnemopi-recall\", { ids: deliveredIds });\n",
|
|
4949
|
+
},
|
|
4950
|
+
{
|
|
4951
|
+
// 2026-09-27 도구 오류 집계(최근 7일 2,372건): `bash` 에 service 이름 없이 `env` 를 주면 거절돼
|
|
4952
|
+
// 헛턴 하나를 썼다(사흘간 12건). 일반 명령에서는 env 를 명령 앞 `export` 로 바꿔 모든 실행
|
|
4953
|
+
// 경로(셸·PTY·client terminal)에 같게 적용한다. ready 는 service 전용으로 그대로 둔다.
|
|
4954
|
+
file: "src/tools/bash.ts",
|
|
4955
|
+
marker: "// HANSE: env without a service name exports into the command",
|
|
4956
|
+
anchor: "\t\t} else if (ready !== undefined || env !== undefined) {\n\t\t\tthrow new ToolError(\"ready and env require a service name.\");\n",
|
|
4957
|
+
patched: "\t\t} else if (ready !== undefined) {\n\t\t\t// HANSE: env without a service name exports into the command\n\t\t\tthrow new ToolError(\"ready requires a service name.\");\n",
|
|
4958
|
+
},
|
|
4959
|
+
{
|
|
4960
|
+
// 위 항목의 짝. 명령 검사(interceptor)·승인·worktree 재작성이 끝난 원래 명령 앞에만 붙인다.
|
|
4961
|
+
file: "src/tools/bash.ts",
|
|
4962
|
+
marker: "// HANSE: non-service env becomes a leading export",
|
|
4963
|
+
anchor: "\t\tinvalidateGithubCacheForBashCommand(command);\n",
|
|
4964
|
+
patched: ` invalidateGithubCacheForBashCommand(command);
|
|
4965
|
+
// HANSE: non-service env becomes a leading export
|
|
4966
|
+
if (name === undefined && env) {
|
|
4967
|
+
const exports: string[] = [];
|
|
4968
|
+
for (const [key, value] of Object.entries(env)) {
|
|
4969
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) throw new ToolError("Invalid env name: " + key);
|
|
4970
|
+
exports.push("export " + key + "='" + value.replaceAll("'", "'\\\\''") + "'");
|
|
4971
|
+
}
|
|
4972
|
+
if (exports.length > 0) command = exports.join("; ") + "; " + command;
|
|
4973
|
+
}
|
|
4974
|
+
`,
|
|
4975
|
+
},
|
|
4976
|
+
{
|
|
4977
|
+
file: "src/prompts/tools/bash.md",
|
|
4978
|
+
marker: "ready requires name; no async/timeout. env adds variables (without name: exported before the command)",
|
|
4979
|
+
anchor: "unique name; ready/env require name; no async/timeout. env adds variables;",
|
|
4980
|
+
patched: "unique name; ready requires name; no async/timeout. env adds variables (without name: exported before the command);",
|
|
4981
|
+
},
|
|
4982
|
+
{
|
|
4983
|
+
// 같은 집계: `todo` append 에 phase 를 빠뜨리면 거절됐다(사흘간 9건). phase 가 없으면 아직 안 끝난
|
|
4984
|
+
// 일이 있는 첫 phase, 없으면 마지막 phase, phase 가 없으면 init 기본 이름에 붙인다.
|
|
4985
|
+
file: "src/tools/todo.ts",
|
|
4986
|
+
marker: "// HANSE: append without phase targets the active phase",
|
|
4987
|
+
anchor: "\tif (!entry.phase) {\n\t\terrors.push(\"Missing phase name for append operation\");\n\t\treturn phases;\n\t}\n",
|
|
4988
|
+
patched: "\t// HANSE: append without phase targets the active phase\n\tconst targetPhase =\n\t\tentry.phase ||\n\t\tphases.find(phase => phase.tasks.some(task => task.status === \"in_progress\" || task.status === \"pending\" || task.status === \"blocked\"))?.name ||\n\t\tphases.at(-1)?.name ||\n\t\tDEFAULT_INIT_PHASE;\n",
|
|
4989
|
+
},
|
|
4990
|
+
{
|
|
4991
|
+
file: "src/tools/todo.ts",
|
|
4992
|
+
marker: "phase = { name: targetPhase, tasks: [] };",
|
|
4993
|
+
anchor: "\tlet phase = findPhaseByName(phases, entry.phase);\n\tif (!phase) {\n\t\tphase = { name: entry.phase, tasks: [] };\n",
|
|
4994
|
+
patched: "\tlet phase = findPhaseByName(phases, targetPhase);\n\tif (!phase) {\n\t\tphase = { name: targetPhase, tasks: [] };\n",
|
|
4995
|
+
},
|
|
4996
|
+
{
|
|
4997
|
+
// 2026-09-27 edit 거절 조사: 7일 거절 174건 중 68건이 직전 edit 응답의 새 태그를 기준으로 했다. edit 가
|
|
4998
|
+
// 성공하면 새 스냅샷은 응답에 보인 몇 줄만 '본 줄' 로 갖고, 전에 읽은 바뀌지 않은 줄은 잃었다(1~43줄 read →
|
|
4999
|
+
// 5줄 edit → 30줄 edit 거절로 재현). 이전 태그의 본 줄을 두 스냅샷 사이 줄 대응으로 옮겨 새 태그에 더한다.
|
|
5000
|
+
// 바뀐 줄은 옮기지 않으므로 '보지 않은 줄은 고치지 않는다' 는 가드 취지는 그대로다.
|
|
5001
|
+
file: "src/edit/index.ts",
|
|
5002
|
+
marker: "\tdiffLineRuns, // HANSE: seen-line carry\n",
|
|
5003
|
+
anchor: "\tEditSession,\n\teditDescription,\n",
|
|
5004
|
+
patched: "\tEditSession,\n\tdiffLineRuns, // HANSE: seen-line carry\n\teditDescription,\n",
|
|
5005
|
+
},
|
|
5006
|
+
{
|
|
5007
|
+
file: "src/edit/index.ts",
|
|
5008
|
+
marker: "// HANSE: carry seen lines across the edit",
|
|
5009
|
+
anchor: "\t\tconst details = aggregateDetails(outcome.files, this.mode);\n",
|
|
5010
|
+
patched: ` // HANSE: carry seen lines across the edit
|
|
5011
|
+
const editInput = (params as { input?: unknown }).input;
|
|
5012
|
+
if (this.mode === "hashline" && typeof editInput === "string") {
|
|
5013
|
+
const store = getEditStore(this.session);
|
|
5014
|
+
const pathKey = (value: string) => (process.platform === "win32" ? path.resolve(value).toLowerCase() : path.resolve(value));
|
|
5015
|
+
const baseTags = new Map<string, string>();
|
|
5016
|
+
for (const match of editInput.matchAll(/^\\[([^\\]\\n#]+)#([0-9A-Fa-f]{4})\\]/gm)) {
|
|
5017
|
+
baseTags.set(pathKey(path.resolve(this.session.cwd, match[1].trim())), match[2].toUpperCase());
|
|
5018
|
+
}
|
|
5019
|
+
for (const file of outcome.files) {
|
|
5020
|
+
if (file.op !== "update" || file.moveTo) continue;
|
|
5021
|
+
const baseTag = baseTags.get(pathKey(file.path));
|
|
5022
|
+
const newTag = store.headHash(file.path);
|
|
5023
|
+
if (!baseTag || !newTag || newTag.toUpperCase() === baseTag) continue;
|
|
5024
|
+
const seen = store.seenLines(file.path, baseTag);
|
|
5025
|
+
const baseText = store.byHashText(file.path, baseTag);
|
|
5026
|
+
const newText = store.headText(file.path);
|
|
5027
|
+
if (!seen || seen.length === 0 || baseText === null || newText === null) continue;
|
|
5028
|
+
const seenSet = new Set(seen);
|
|
5029
|
+
const carried: number[] = [];
|
|
5030
|
+
let oldLine = 1;
|
|
5031
|
+
let newLine = 1;
|
|
5032
|
+
for (const run of diffLineRuns(baseText.replace(/\\r\\n/g, "\\n"), newText.replace(/\\r\\n/g, "\\n"))) {
|
|
5033
|
+
if (run.added) newLine += run.count;
|
|
5034
|
+
else if (run.removed) oldLine += run.count;
|
|
5035
|
+
else {
|
|
5036
|
+
for (let offset = 0; offset < run.count; offset++) if (seenSet.has(oldLine + offset)) carried.push(newLine + offset);
|
|
5037
|
+
oldLine += run.count;
|
|
5038
|
+
newLine += run.count;
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
if (carried.length > 0) store.recordSeenLines(file.path, newTag, carried);
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
const details = aggregateDetails(outcome.files, this.mode);
|
|
5045
|
+
`,
|
|
5046
|
+
},
|
|
4820
5047
|
{
|
|
4821
5048
|
// 2026-09-26 실측: `learn`/`retain` 은 `extract: true` 로 저장돼 원문 기억 1건에서 문장 단위 fact 가
|
|
4822
5049
|
// 파생된다(facts.source_msg_id = 원문 working id). fact 회수 결과에는 그 연결이 빠져 있어서, 세션 첫
|