@miller-tech/uap 1.130.1 → 1.131.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli/deliver.js +25 -12
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/handsfree.d.ts.map +1 -1
- package/dist/cli/handsfree.js +12 -1
- package/dist/cli/handsfree.js.map +1 -1
- package/dist/cli/policy.d.ts.map +1 -1
- package/dist/cli/policy.js +17 -3
- package/dist/cli/policy.js.map +1 -1
- package/dist/delivery/decompose.d.ts.map +1 -1
- package/dist/delivery/decompose.js +4 -1
- package/dist/delivery/decompose.js.map +1 -1
- package/dist/delivery/epic-controller.d.ts +20 -1
- package/dist/delivery/epic-controller.d.ts.map +1 -1
- package/dist/delivery/epic-controller.js +21 -10
- package/dist/delivery/epic-controller.js.map +1 -1
- package/dist/delivery/persistence-profile.d.ts.map +1 -1
- package/dist/delivery/persistence-profile.js +19 -2
- package/dist/delivery/persistence-profile.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +132 -4
- package/tools/agents/tests/test_prune_preserve_force_write.py +162 -0
|
@@ -20,7 +20,10 @@ function maxPhases() {
|
|
|
20
20
|
const raw = Number(process.env.UAP_DELIVER_MAX_PHASES);
|
|
21
21
|
if (Number.isFinite(raw) && raw >= MIN_PHASES)
|
|
22
22
|
return Math.min(Math.floor(raw), MAX_PHASES_HARD_CEILING);
|
|
23
|
-
|
|
23
|
+
// (#4a) Default 8→10: a huge mission was being squeezed into ≤8 phases, each
|
|
24
|
+
// then too big for a rail. Still bounded by the hard ceiling; env overrides up
|
|
25
|
+
// to 20 for the largest missions.
|
|
26
|
+
return 10;
|
|
24
27
|
}
|
|
25
28
|
/** Only instructions this long are epic-shaped enough to auto-decompose. */
|
|
26
29
|
const AUTO_DECOMPOSE_MIN_CHARS = 200;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"decompose.js","sourceRoot":"","sources":["../../src/delivery/decompose.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAqBH,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB;;;;;GAKG;AACH,MAAM,uBAAuB,GAAG,EAAE,CAAC;AACnC,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC;IACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,UAAU;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,uBAAuB,CAAC,CAAC;IACzG,OAAO,
|
|
1
|
+
{"version":3,"file":"decompose.js","sourceRoot":"","sources":["../../src/delivery/decompose.ts"],"names":[],"mappings":"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"}
|
|
@@ -62,8 +62,27 @@ export interface EpicControllerConfig {
|
|
|
62
62
|
* treated as "not accepted" so the controller retries rather than wedging.
|
|
63
63
|
*/
|
|
64
64
|
checkAcceptance?: (epic: Epic, result: EpicRunResult) => boolean | Promise<boolean>;
|
|
65
|
-
/** Max fresh-session attempts per epic before it is declared failed. Default
|
|
65
|
+
/** Max fresh-session attempts per epic before it is declared failed. Default 3. */
|
|
66
66
|
maxAttemptsPerEpic?: number;
|
|
67
|
+
/**
|
|
68
|
+
* (#4c) Max recursive split levels when an epic can't be delivered whole.
|
|
69
|
+
* Default 1 (one level: an over-budget epic splits into sub-epics, which
|
|
70
|
+
* never re-split). deliver raises this for LARGE missions so a genuinely huge
|
|
71
|
+
* epic recursively shrinks — a sub-epic that still can't land is split again,
|
|
72
|
+
* one level shallower — until each piece fits a rail, instead of failing the
|
|
73
|
+
* whole mission at the first split. Bounded: the depth counter decrements to
|
|
74
|
+
* 0, and each `splitEpic` planner can decline (return null) to stop early.
|
|
75
|
+
*/
|
|
76
|
+
splitDepth?: number;
|
|
77
|
+
/**
|
|
78
|
+
* (#5) When true, split a failed epic on ANY exhausted-attempts failure, not
|
|
79
|
+
* only on context-budget exhaustion — turning "epic failed, mission
|
|
80
|
+
* incomplete" into "re-plan it into smaller pieces and try those." Default
|
|
81
|
+
* false (budget exhaustion only, preserving the conservative path). deliver
|
|
82
|
+
* enables it for large missions so give-up becomes auto-escalation. Still
|
|
83
|
+
* bounded by `splitDepth` and by the planner's option to decline the split.
|
|
84
|
+
*/
|
|
85
|
+
splitOnAnyFailure?: boolean;
|
|
67
86
|
/** Progress hook. */
|
|
68
87
|
onEpic?: (epic: Epic, outcome: EpicOutcome) => void;
|
|
69
88
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epic-controller.d.ts","sourceRoot":"","sources":["../../src/delivery/epic-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,4EAA4E;AAC5E,MAAM,WAAW,IAAK,SAAQ,aAAa;IACzC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;;;OAKG;IACH,OAAO,EAAE,CACP,IAAI,EAAE,IAAI,EACV,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KACrE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB;IACrB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACpD;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;IACzE;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAID;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"epic-controller.d.ts","sourceRoot":"","sources":["../../src/delivery/epic-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,4EAA4E;AAC5E,MAAM,WAAW,IAAK,SAAQ,aAAa;IACzC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,OAAO,CAAC;IACjB,iFAAiF;IACjF,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd;;;;;OAKG;IACH,OAAO,EAAE,CACP,IAAI,EAAE,IAAI,EACV,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,KACrE,OAAO,CAAC,aAAa,CAAC,CAAC;IAC5B;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACpF,mFAAmF;IACnF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;OAOG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB;IACrB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;IACpD;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,CAAC;IACzE;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,WAAW,EAAE,CAAC;CACzB;AAID;;;;;GAKG;AACH,wBAAsB,QAAQ,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAgJ1F"}
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { topoOrder } from './decompose.js';
|
|
24
24
|
import { CONTEXT_BUDGET_MARKER } from './context-budget.js';
|
|
25
|
-
const DEFAULT_MAX_ATTEMPTS =
|
|
25
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
26
26
|
/**
|
|
27
27
|
* Run the epic sequence. Epics execute in dependency order; an epic whose
|
|
28
28
|
* dependency failed is skipped (never build an epic on an unfinished one). Each
|
|
@@ -85,14 +85,24 @@ export async function runEpics(config) {
|
|
|
85
85
|
: `attempt ${attempts} failed to deliver: ${result.summary}`;
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
-
// Rail sizing: an epic that
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
88
|
+
// Rail sizing + auto-escalation: an epic that exhausted its attempts is
|
|
89
|
+
// re-planned as smaller sub-epics and run in place, rather than failing the
|
|
90
|
+
// whole mission. Fires on context-budget exhaustion always, and on ANY
|
|
91
|
+
// failure when splitOnAnyFailure is set (#5). Recurses up to `splitDepth`
|
|
92
|
+
// levels (#4c): sub-epics inherit the prior summaries and may split again
|
|
93
|
+
// one level shallower until each piece fits a rail. Default depth 1 keeps
|
|
94
|
+
// the conservative one-level behavior.
|
|
95
|
+
const depthRemaining = config.splitDepth ?? 1;
|
|
96
|
+
const budgetExhausted = (lastFailure ?? '').includes(CONTEXT_BUDGET_MARKER);
|
|
97
|
+
const splitFn = config.splitEpic;
|
|
98
|
+
const shouldSplit = !accepted &&
|
|
99
|
+
!!splitFn &&
|
|
100
|
+
depthRemaining > 0 &&
|
|
101
|
+
(budgetExhausted || config.splitOnAnyFailure === true);
|
|
102
|
+
if (shouldSplit && splitFn) {
|
|
93
103
|
let subs = null;
|
|
94
104
|
try {
|
|
95
|
-
subs = await
|
|
105
|
+
subs = await splitFn(epic, lastFailure);
|
|
96
106
|
}
|
|
97
107
|
catch {
|
|
98
108
|
subs = null; // fail-soft: an unplannable split just keeps the failure
|
|
@@ -113,7 +123,7 @@ export async function runEpics(config) {
|
|
|
113
123
|
const subResult = await runEpics({
|
|
114
124
|
...config,
|
|
115
125
|
epics: chained,
|
|
116
|
-
|
|
126
|
+
splitDepth: depthRemaining - 1, // recurse one level shallower (#4c); 0 ⇒ no re-split
|
|
117
127
|
initialPriorSummaries: [...priorSummaries],
|
|
118
128
|
});
|
|
119
129
|
epicTurns += subResult.turns; // flows into totalTurns below
|
|
@@ -124,9 +134,10 @@ export async function runEpics(config) {
|
|
|
124
134
|
// project gates, i.e. the epic's goal is delivered.
|
|
125
135
|
const finalPieceGreen = subResult.outcomes.length > 0 && subResult.outcomes[subResult.outcomes.length - 1].accepted;
|
|
126
136
|
accepted = subResult.success || finalPieceGreen;
|
|
137
|
+
const splitReason = budgetExhausted ? 'context auto-size' : 'auto-escalation';
|
|
127
138
|
lastSummary = accepted
|
|
128
|
-
? `split into ${chained.length} sub-epics (
|
|
129
|
-
: `split into ${chained.length} sub-epics
|
|
139
|
+
? `split into ${chained.length} sub-epics (${splitReason}): ${subResult.outcomes.map((o) => o.summary).join('; ')}`
|
|
140
|
+
: `split into ${chained.length} sub-epics (${splitReason}); failed: ${subResult.failed.join(', ')}`;
|
|
130
141
|
}
|
|
131
142
|
}
|
|
132
143
|
totalTurns += epicTurns;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epic-controller.js","sourceRoot":"","sources":["../../src/delivery/epic-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"epic-controller.js","sourceRoot":"","sources":["../../src/delivery/epic-controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence-profile.d.ts","sourceRoot":"","sources":["../../src/delivery/persistence-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,CAAC;AAC/E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,oBAAoB,CAAC;IAChC,iFAAiF;IACjF,cAAc,EAAE,OAAO,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"persistence-profile.d.ts","sourceRoot":"","sources":["../../src/delivery/persistence-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,MAAM,oBAAoB,GAAG,KAAK,GAAG,OAAO,GAAG,UAAU,GAAG,YAAY,CAAC;AAC/E,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAErE,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,oBAAoB,CAAC;IAChC,iFAAiF;IACjF,cAAc,EAAE,OAAO,CAAC;IACxB,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,eAAe,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;CACzB;AAsBD,wEAAwE;AACxE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAMhE;AAeD,MAAM,WAAW,iBAAiB;IAChC,2DAA2D;IAC3D,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,sDAAsD;IACtD,SAAS,CAAC,EAAE,oBAAoB,CAAC;IACjC,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACxD;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,GAAE,iBAAsB,GAC1B,kBAAkB,CAoCpB"}
|
|
@@ -18,8 +18,11 @@ const PROFILES = {
|
|
|
18
18
|
light: { intensity: 'light', stopHookBlocks: true, maxBlocks: 3, stagnationLimit: 2, injectAutonomy: false },
|
|
19
19
|
// Opus/Sonnet/GPT — nudge past premature-done; moderate blocking.
|
|
20
20
|
moderate: { intensity: 'moderate', stopHookBlocks: true, maxBlocks: 6, stagnationLimit: 3, injectAutonomy: true },
|
|
21
|
-
// qwen / small local — firmly drive; more blocks tolerated
|
|
22
|
-
|
|
21
|
+
// qwen / small local — firmly drive; more blocks tolerated. (#4d) maxBlocks
|
|
22
|
+
// 9→12 and stagnationLimit 3→4 give a huge multi-epic build more runway
|
|
23
|
+
// before give-up, now that #2 (state carryover) and #3 (force-write) keep the
|
|
24
|
+
// model making real progress across resets. Still finite — anti-spin holds.
|
|
25
|
+
aggressive: { intensity: 'aggressive', stopHookBlocks: true, maxBlocks: 12, stagnationLimit: 4, injectAutonomy: true },
|
|
23
26
|
};
|
|
24
27
|
const OFF_PROFILE = {
|
|
25
28
|
intensity: 'off',
|
|
@@ -73,6 +76,20 @@ export function resolvePersistenceProfile(modelId, cfg = {}) {
|
|
|
73
76
|
}
|
|
74
77
|
}
|
|
75
78
|
}
|
|
79
|
+
// (#4d) Env caps: raise the hands-free runway for a huge mission without
|
|
80
|
+
// weakening per-family defaults. Applied last so an explicit operator value
|
|
81
|
+
// wins. Only meaningful when the profile blocks; both stay finite (clamped
|
|
82
|
+
// to 50) so the "never spin forever" guarantee holds.
|
|
83
|
+
if (profile.stopHookBlocks) {
|
|
84
|
+
const envBlocks = Number(process.env.UAP_HANDSFREE_MAX_BLOCKS);
|
|
85
|
+
const envStag = Number(process.env.UAP_HANDSFREE_STAGNATION_LIMIT);
|
|
86
|
+
if (Number.isFinite(envBlocks) && envBlocks >= 1) {
|
|
87
|
+
profile = { ...profile, maxBlocks: Math.min(Math.floor(envBlocks), 50) };
|
|
88
|
+
}
|
|
89
|
+
if (Number.isFinite(envStag) && envStag >= 1) {
|
|
90
|
+
profile = { ...profile, stagnationLimit: Math.min(Math.floor(envStag), 50) };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
76
93
|
return profile;
|
|
77
94
|
}
|
|
78
95
|
//# sourceMappingURL=persistence-profile.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence-profile.js","sourceRoot":"","sources":["../../src/delivery/persistence-profile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAmBH,MAAM,QAAQ,GAA+F;IAC3G,qEAAqE;IACrE,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,cAAc,EAAE,KAAK,EAAE;IAC5G,kEAAkE;IAClE,QAAQ,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,eAAe,EAAE,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE;IACjH,
|
|
1
|
+
{"version":3,"file":"persistence-profile.js","sourceRoot":"","sources":["../../src/delivery/persistence-profile.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -4645,6 +4645,7 @@ def _maybe_inject_recon_convergence(
|
|
|
4645
4645
|
openai_body["messages"] = msgs
|
|
4646
4646
|
|
|
4647
4647
|
restored: list[str] = []
|
|
4648
|
+
stripped: list[str] = []
|
|
4648
4649
|
if escalate:
|
|
4649
4650
|
# Strip tools entirely so the only possible response is terminal prose.
|
|
4650
4651
|
openai_body.pop("tools", None)
|
|
@@ -4691,11 +4692,44 @@ def _maybe_inject_recon_convergence(
|
|
|
4691
4692
|
present.add(name.lower())
|
|
4692
4693
|
restored.append(name)
|
|
4693
4694
|
|
|
4695
|
+
# FORCE-WRITE (hard tier): a nudge that leaves BOTH read and write tools
|
|
4696
|
+
# available lets the model keep picking a read — the live failure mode
|
|
4697
|
+
# (no_write_streak climbing to 45+ while write tools were re-injected but
|
|
4698
|
+
# tool_choice stayed 'auto', so the model just read again). At the hard
|
|
4699
|
+
# tier, remove the exploration path (read-only class + escape hatches) so
|
|
4700
|
+
# the only actions left are write/deliver, and force tool_choice=required
|
|
4701
|
+
# so a write MUST happen this turn. Floor-guarded: if stripping would
|
|
4702
|
+
# leave no write path, leave the toolset untouched (never strand — the
|
|
4703
|
+
# firm tier keeps reads, the escalate tier strips everything). Bounded:
|
|
4704
|
+
# if the model still produces no write, the streak keeps climbing and the
|
|
4705
|
+
# next fire escalates to the terminal-summary tier above.
|
|
4706
|
+
if hard:
|
|
4707
|
+
tools_now = openai_body.get("tools") or []
|
|
4708
|
+
deny = {c.lower() for c in _READ_ONLY_TOOL_CLASS} | {
|
|
4709
|
+
e.lower() for e in _EXPLORATION_ESCAPE_TOOLS
|
|
4710
|
+
}
|
|
4711
|
+
write_lower = {w.lower() for w in _WRITE_TOOL_CLASS}
|
|
4712
|
+
|
|
4713
|
+
def _rc_name(t: dict) -> str:
|
|
4714
|
+
return ((t.get("function", {}) or {}).get("name", "") or "").lower()
|
|
4715
|
+
|
|
4716
|
+
kept = [t for t in tools_now if _rc_name(t) not in deny]
|
|
4717
|
+
if kept and any(_rc_name(t) in write_lower for t in kept):
|
|
4718
|
+
stripped = [
|
|
4719
|
+
((t.get("function", {}) or {}).get("name", "") or "")
|
|
4720
|
+
for t in tools_now
|
|
4721
|
+
if _rc_name(t) in deny
|
|
4722
|
+
]
|
|
4723
|
+
if stripped:
|
|
4724
|
+
openai_body["tools"] = kept
|
|
4725
|
+
openai_body["tool_choice"] = "required"
|
|
4726
|
+
openai_body.pop("grammar", None)
|
|
4727
|
+
|
|
4694
4728
|
logger.warning(
|
|
4695
4729
|
"RECON CONVERGENCE: injected %s directive (no_write_streak=%d, hard_fires=%d, "
|
|
4696
|
-
"ctx=%.0f%%, tool_choice=%s, restored_write_tools=%s)",
|
|
4730
|
+
"ctx=%.0f%%, tool_choice=%s, restored_write_tools=%s, stripped_read_tools=%s)",
|
|
4697
4731
|
tier, streak, monitor.recon_hard_fires, util * 100,
|
|
4698
|
-
openai_body.get("tool_choice", "stripped"), restored or "none",
|
|
4732
|
+
openai_body.get("tool_choice", "stripped"), restored or "none", stripped or "none",
|
|
4699
4733
|
)
|
|
4700
4734
|
|
|
4701
4735
|
|
|
@@ -8151,6 +8185,94 @@ async def _apply_malformed_tool_guardrail(
|
|
|
8151
8185
|
)
|
|
8152
8186
|
|
|
8153
8187
|
|
|
8188
|
+
def _extract_state_carryover(messages: list[dict], max_files: int = 20) -> str | None:
|
|
8189
|
+
"""Reconstruct mission state (plan + files written) from a conversation that
|
|
8190
|
+
is about to be pruned by the contamination breaker, so the reset does not
|
|
8191
|
+
throw away what the build has planned and done.
|
|
8192
|
+
|
|
8193
|
+
The contamination/attractor reset keeps only the first user turn (+ a short
|
|
8194
|
+
tail), dropping the middle of the conversation — which is where the TodoWrite
|
|
8195
|
+
plan and the file-write actions live. On a huge build that drop destroys
|
|
8196
|
+
mission state: the model then re-explores from scratch, regrows the history,
|
|
8197
|
+
overflows again, and thrashes. The client's on-disk ledger is not visible to
|
|
8198
|
+
the proxy, but the plan and the writes ARE in the message stream, so
|
|
8199
|
+
reconstruct a compact carryover from them and re-inject it after the reset.
|
|
8200
|
+
|
|
8201
|
+
Returns a carryover block, or None when no state is found (fresh session).
|
|
8202
|
+
"""
|
|
8203
|
+
write_lower = {w.lower() for w in _WRITE_TOOL_CLASS}
|
|
8204
|
+
plan_tools = {
|
|
8205
|
+
"todowrite", "taskcreate", "taskupdate", "task_create", "task_update",
|
|
8206
|
+
"todo_write", "update_todo_list",
|
|
8207
|
+
}
|
|
8208
|
+
latest_plan: list[str] = []
|
|
8209
|
+
files: list[str] = []
|
|
8210
|
+
seen: set[str] = set()
|
|
8211
|
+
for m in messages:
|
|
8212
|
+
if not isinstance(m, dict) or m.get("role") != "assistant":
|
|
8213
|
+
continue
|
|
8214
|
+
content = m.get("content")
|
|
8215
|
+
if not isinstance(content, list):
|
|
8216
|
+
continue
|
|
8217
|
+
for block in content:
|
|
8218
|
+
if not isinstance(block, dict) or block.get("type") != "tool_use":
|
|
8219
|
+
continue
|
|
8220
|
+
name = (block.get("name") or "").lower()
|
|
8221
|
+
inp = block.get("input") if isinstance(block.get("input"), dict) else {}
|
|
8222
|
+
if name in plan_tools:
|
|
8223
|
+
todos = inp.get("todos") or inp.get("tasks") or inp.get("items")
|
|
8224
|
+
if isinstance(todos, list):
|
|
8225
|
+
rendered = []
|
|
8226
|
+
for t in todos:
|
|
8227
|
+
if not isinstance(t, dict):
|
|
8228
|
+
continue
|
|
8229
|
+
text = (
|
|
8230
|
+
t.get("content") or t.get("title")
|
|
8231
|
+
or t.get("task") or t.get("description") or ""
|
|
8232
|
+
).strip()
|
|
8233
|
+
if not text:
|
|
8234
|
+
continue
|
|
8235
|
+
status = (t.get("status") or t.get("state") or "").strip().lower()
|
|
8236
|
+
mark = {
|
|
8237
|
+
"completed": "x", "done": "x", "complete": "x",
|
|
8238
|
+
"in_progress": "~", "in-progress": "~", "active": "~",
|
|
8239
|
+
}.get(status, " ")
|
|
8240
|
+
rendered.append(f" [{mark}] {text}")
|
|
8241
|
+
if rendered:
|
|
8242
|
+
latest_plan = rendered # keep the MOST RECENT plan only
|
|
8243
|
+
elif name in write_lower:
|
|
8244
|
+
path = (
|
|
8245
|
+
inp.get("file_path") or inp.get("path")
|
|
8246
|
+
or inp.get("target") or inp.get("filename") or inp.get("task")
|
|
8247
|
+
)
|
|
8248
|
+
if isinstance(path, str) and path.strip() and path not in seen:
|
|
8249
|
+
seen.add(path)
|
|
8250
|
+
files.append(path.strip())
|
|
8251
|
+
if not latest_plan and not files:
|
|
8252
|
+
return None
|
|
8253
|
+
parts = [
|
|
8254
|
+
"[STATE CARRYOVER — preserved across the reset; do NOT restart from "
|
|
8255
|
+
"scratch or re-explore what is already done]"
|
|
8256
|
+
]
|
|
8257
|
+
if latest_plan:
|
|
8258
|
+
parts.append(
|
|
8259
|
+
"Your current plan (most recent TodoWrite) — resume the first "
|
|
8260
|
+
"unfinished item ([ ] = todo, [~] = in progress, [x] = done):"
|
|
8261
|
+
)
|
|
8262
|
+
parts.extend(latest_plan[:40])
|
|
8263
|
+
if files:
|
|
8264
|
+
shown = files[-max_files:]
|
|
8265
|
+
omitted = len(files) - len(shown)
|
|
8266
|
+
head_note = f" (showing last {len(shown)} of {len(files)})" if omitted > 0 else ""
|
|
8267
|
+
parts.append(f"Files you have already written/edited this session{head_note}:")
|
|
8268
|
+
parts.extend(f" - {p}" for p in shown)
|
|
8269
|
+
parts.append(
|
|
8270
|
+
"Continue the plan from where it stands. Take the next concrete action "
|
|
8271
|
+
"toward the first unfinished item now."
|
|
8272
|
+
)
|
|
8273
|
+
return "\n".join(parts)
|
|
8274
|
+
|
|
8275
|
+
|
|
8154
8276
|
def _maybe_apply_session_contamination_breaker(
|
|
8155
8277
|
anthropic_body: dict, monitor: SessionMonitor, session_id: str
|
|
8156
8278
|
) -> dict:
|
|
@@ -8283,7 +8405,11 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
8283
8405
|
"Just call the tool."
|
|
8284
8406
|
),
|
|
8285
8407
|
}
|
|
8286
|
-
|
|
8408
|
+
carry = _extract_state_carryover(messages)
|
|
8409
|
+
new_messages = (
|
|
8410
|
+
head + [reset_marker, {"role": "user", "content": carry}]
|
|
8411
|
+
if carry else head + [reset_marker]
|
|
8412
|
+
)
|
|
8287
8413
|
monitor.attractor_correction_active = True
|
|
8288
8414
|
log_reason = "attractor"
|
|
8289
8415
|
else:
|
|
@@ -8296,7 +8422,9 @@ def _maybe_apply_session_contamination_breaker(
|
|
|
8296
8422
|
"Continue from the recent context and emit valid tool calls with strict JSON arguments only.]"
|
|
8297
8423
|
),
|
|
8298
8424
|
}
|
|
8299
|
-
|
|
8425
|
+
carry = _extract_state_carryover(messages)
|
|
8426
|
+
carry_msgs = [{"role": "user", "content": carry}] if carry else []
|
|
8427
|
+
new_messages = head + [reset_marker] + carry_msgs + tail
|
|
8300
8428
|
log_reason = "standard"
|
|
8301
8429
|
|
|
8302
8430
|
updated_body = dict(anthropic_body)
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Tests for the huge-project hands-free fixes:
|
|
2
|
+
|
|
3
|
+
#3 FORCE-WRITE: at the hard recon-convergence tier the proxy strips the
|
|
4
|
+
exploration tools (read-only class + escape hatches) and forces
|
|
5
|
+
tool_choice='required' so the model MUST write instead of reading again.
|
|
6
|
+
#2 STATE CARRYOVER: the contamination breaker reconstructs the plan + files
|
|
7
|
+
written from the message stream and re-injects them after the reset, so a
|
|
8
|
+
prune no longer destroys mission state.
|
|
9
|
+
"""
|
|
10
|
+
import importlib.util
|
|
11
|
+
import unittest
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
proxy_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "anthropic_proxy.py"
|
|
15
|
+
spec = importlib.util.spec_from_file_location("anthropic_proxy", proxy_path)
|
|
16
|
+
ap = importlib.util.module_from_spec(spec)
|
|
17
|
+
spec.loader.exec_module(ap)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _tool(name):
|
|
21
|
+
return {"type": "function", "function": {"name": name, "parameters": {}}}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _names(tools):
|
|
25
|
+
return {(t.get("function", {}) or {}).get("name", "") for t in tools}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ForceWriteHardTierTest(unittest.TestCase):
|
|
29
|
+
"""#3: hard tier removes read/exploration tools and forces a write."""
|
|
30
|
+
|
|
31
|
+
def _hard_monitor(self):
|
|
32
|
+
m = ap.SessionMonitor(context_window=132096)
|
|
33
|
+
m.consecutive_no_write_turns = (
|
|
34
|
+
int(ap.PROXY_RECON_HARD_MULTIPLIER * ap.PROXY_RECON_CONVERGENCE_THRESHOLD) + 1
|
|
35
|
+
)
|
|
36
|
+
return m
|
|
37
|
+
|
|
38
|
+
def test_strips_reads_and_forces_required(self):
|
|
39
|
+
m = self._hard_monitor()
|
|
40
|
+
tools = [_tool("Read"), _tool("Grep"), _tool("Bash"), _tool("Write"), _tool("Edit")]
|
|
41
|
+
body = {"messages": [{"role": "user", "content": "reading"}], "tools": list(tools)}
|
|
42
|
+
ap._maybe_inject_recon_convergence(body, m, full_tools=tools)
|
|
43
|
+
remaining = _names(body["tools"])
|
|
44
|
+
self.assertNotIn("Read", remaining)
|
|
45
|
+
self.assertNotIn("Grep", remaining)
|
|
46
|
+
self.assertNotIn("Bash", remaining) # exploration escape hatch also stripped
|
|
47
|
+
self.assertIn("Write", remaining)
|
|
48
|
+
self.assertIn("Edit", remaining)
|
|
49
|
+
self.assertEqual(body.get("tool_choice"), "required")
|
|
50
|
+
|
|
51
|
+
def test_keeps_deliver_when_writes_are_gated(self):
|
|
52
|
+
m = self._hard_monitor()
|
|
53
|
+
tools = [_tool("Read"), _tool("Bash"), _tool("deliver")]
|
|
54
|
+
body = {"messages": [{"role": "user", "content": "reading"}], "tools": list(tools)}
|
|
55
|
+
ap._maybe_inject_recon_convergence(body, m, full_tools=tools)
|
|
56
|
+
remaining = _names(body["tools"])
|
|
57
|
+
self.assertIn("deliver", remaining) # deliver is a write path — survives
|
|
58
|
+
self.assertNotIn("Read", remaining)
|
|
59
|
+
self.assertEqual(body.get("tool_choice"), "required")
|
|
60
|
+
|
|
61
|
+
def test_floor_invariant_no_write_path_left_untouched(self):
|
|
62
|
+
"""If stripping reads would leave NO write path, do not strip (never
|
|
63
|
+
strand). Falls back to the hard tier's 'auto' release."""
|
|
64
|
+
m = self._hard_monitor()
|
|
65
|
+
tools = [_tool("Read"), _tool("Grep")] # read-only, no write tool
|
|
66
|
+
body = {"messages": [{"role": "user", "content": "reading"}], "tools": list(tools)}
|
|
67
|
+
ap._maybe_inject_recon_convergence(body, m, full_tools=tools)
|
|
68
|
+
remaining = _names(body["tools"])
|
|
69
|
+
self.assertIn("Read", remaining) # untouched — no write path to force toward
|
|
70
|
+
self.assertNotEqual(body.get("tool_choice"), "required")
|
|
71
|
+
|
|
72
|
+
def test_firm_tier_does_not_force_write(self):
|
|
73
|
+
"""The firm tier (below the hard multiplier) must keep reads available."""
|
|
74
|
+
m = ap.SessionMonitor(context_window=132096)
|
|
75
|
+
m.consecutive_no_write_turns = ap.PROXY_RECON_CONVERGENCE_THRESHOLD # firm
|
|
76
|
+
tools = [_tool("Read"), _tool("Write")]
|
|
77
|
+
body = {"messages": [{"role": "user", "content": "reading"}],
|
|
78
|
+
"tools": list(tools), "tool_choice": "required"}
|
|
79
|
+
ap._maybe_inject_recon_convergence(body, m, full_tools=tools)
|
|
80
|
+
self.assertIn("Read", _names(body["tools"])) # reads still allowed at firm
|
|
81
|
+
self.assertEqual(body.get("tool_choice"), "auto")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class StateCarryoverExtractTest(unittest.TestCase):
|
|
85
|
+
"""#2: _extract_state_carryover reconstructs plan + files from the stream."""
|
|
86
|
+
|
|
87
|
+
def _convo(self):
|
|
88
|
+
return [
|
|
89
|
+
{"role": "user", "content": "build the app"},
|
|
90
|
+
{"role": "assistant", "content": [
|
|
91
|
+
{"type": "tool_use", "name": "TodoWrite", "input": {"todos": [
|
|
92
|
+
{"content": "scaffold project", "status": "completed"},
|
|
93
|
+
{"content": "add auth", "status": "in_progress"},
|
|
94
|
+
{"content": "write tests", "status": "pending"},
|
|
95
|
+
]}},
|
|
96
|
+
]},
|
|
97
|
+
{"role": "assistant", "content": [
|
|
98
|
+
{"type": "tool_use", "name": "Write", "input": {"file_path": "src/index.ts"}},
|
|
99
|
+
]},
|
|
100
|
+
{"role": "assistant", "content": [
|
|
101
|
+
{"type": "tool_use", "name": "Edit", "input": {"file_path": "src/auth.ts"}},
|
|
102
|
+
]},
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
def test_extracts_plan_and_files(self):
|
|
106
|
+
out = ap._extract_state_carryover(self._convo())
|
|
107
|
+
self.assertIsNotNone(out)
|
|
108
|
+
self.assertIn("STATE CARRYOVER", out)
|
|
109
|
+
self.assertIn("scaffold project", out)
|
|
110
|
+
self.assertIn("add auth", out)
|
|
111
|
+
self.assertIn("[x] scaffold project", out)
|
|
112
|
+
self.assertIn("[~] add auth", out)
|
|
113
|
+
self.assertIn("src/index.ts", out)
|
|
114
|
+
self.assertIn("src/auth.ts", out)
|
|
115
|
+
|
|
116
|
+
def test_returns_none_on_fresh_session(self):
|
|
117
|
+
convo = [{"role": "user", "content": "hi"},
|
|
118
|
+
{"role": "assistant", "content": [
|
|
119
|
+
{"type": "tool_use", "name": "Read", "input": {"file_path": "x"}}]}]
|
|
120
|
+
self.assertIsNone(ap._extract_state_carryover(convo))
|
|
121
|
+
|
|
122
|
+
def test_keeps_most_recent_plan_only(self):
|
|
123
|
+
convo = self._convo()
|
|
124
|
+
convo.append({"role": "assistant", "content": [
|
|
125
|
+
{"type": "tool_use", "name": "TodoWrite", "input": {"todos": [
|
|
126
|
+
{"content": "final polish", "status": "pending"}]}}]})
|
|
127
|
+
out = ap._extract_state_carryover(convo)
|
|
128
|
+
self.assertIn("final polish", out)
|
|
129
|
+
self.assertNotIn("scaffold project", out) # superseded by the later plan
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class ContaminationBreakerPreservesStateTest(unittest.TestCase):
|
|
133
|
+
"""#2 end-to-end: a standard contamination reset re-injects the carryover."""
|
|
134
|
+
|
|
135
|
+
def test_standard_reset_includes_carryover(self):
|
|
136
|
+
m = ap.SessionMonitor(context_window=132096)
|
|
137
|
+
m.malformed_tool_streak = max(1, ap.PROXY_SESSION_CONTAMINATION_THRESHOLD)
|
|
138
|
+
keep = max(2, ap.PROXY_SESSION_CONTAMINATION_KEEP_LAST)
|
|
139
|
+
filler = [{"role": "user", "content": f"m{i}"} for i in range(keep + 4)]
|
|
140
|
+
messages = (
|
|
141
|
+
[{"role": "user", "content": "build the app"}]
|
|
142
|
+
+ [{"role": "assistant", "content": [
|
|
143
|
+
{"type": "tool_use", "name": "TodoWrite", "input": {"todos": [
|
|
144
|
+
{"content": "step one", "status": "completed"},
|
|
145
|
+
{"content": "step two", "status": "pending"}]}}]}]
|
|
146
|
+
+ [{"role": "assistant", "content": [
|
|
147
|
+
{"type": "tool_use", "name": "Write", "input": {"file_path": "app/main.py"}}]}]
|
|
148
|
+
+ filler
|
|
149
|
+
)
|
|
150
|
+
body = {"messages": messages, "tools": [_tool("Write")]}
|
|
151
|
+
out = ap._maybe_apply_session_contamination_breaker(body, m, "fp:test")
|
|
152
|
+
blob = "\n".join(
|
|
153
|
+
b if isinstance(b, str) else str(b)
|
|
154
|
+
for msg in out["messages"] for b in [msg.get("content", "")]
|
|
155
|
+
)
|
|
156
|
+
self.assertIn("STATE CARRYOVER", blob)
|
|
157
|
+
self.assertIn("app/main.py", blob)
|
|
158
|
+
self.assertIn("step two", blob)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
if __name__ == "__main__":
|
|
162
|
+
unittest.main()
|