@caupulican/pi-adaptative 0.81.32 → 0.81.35
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 +22 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/README.md +69 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts +53 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.d.ts.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js +1282 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.js.map +1 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/index.ts +1453 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/builder-validator.json +14 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/claude-trio.json +32 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/full-provider-review.json +15 -0
- package/dist/bundled-resources/extensions/tmux-agent-manager/templates/provider-prompt-smoke.json +14 -0
- package/dist/bundled-resources/prompts/extensionify.md +5 -4
- package/dist/bundled-resources/prompts/skillify.md +5 -5
- package/dist/bundled-resources/skills/pi-harness-learning/SKILL.md +6 -3
- package/dist/config.d.ts +5 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -2
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +25 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/background-lane-controller.d.ts +12 -3
- package/dist/core/background-lane-controller.d.ts.map +1 -1
- package/dist/core/background-lane-controller.js +67 -26
- package/dist/core/background-lane-controller.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -3
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/context/context-store-retention.d.ts +4 -1
- package/dist/core/context/context-store-retention.d.ts.map +1 -1
- package/dist/core/context/context-store-retention.js +42 -151
- package/dist/core/context/context-store-retention.js.map +1 -1
- package/dist/core/context-pipeline.d.ts.map +1 -1
- package/dist/core/context-pipeline.js +6 -6
- package/dist/core/context-pipeline.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +25 -39
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/loader.d.ts +9 -2
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +105 -21
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +2 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/goals/goal-state.d.ts +10 -0
- package/dist/core/goals/goal-state.d.ts.map +1 -1
- package/dist/core/goals/goal-state.js +37 -0
- package/dist/core/goals/goal-state.js.map +1 -1
- package/dist/core/goals/goal-tool-core.d.ts +11 -0
- package/dist/core/goals/goal-tool-core.d.ts.map +1 -1
- package/dist/core/goals/goal-tool-core.js +51 -1
- package/dist/core/goals/goal-tool-core.js.map +1 -1
- package/dist/core/improvement-loop.d.ts.map +1 -1
- package/dist/core/improvement-loop.js +26 -5
- package/dist/core/improvement-loop.js.map +1 -1
- package/dist/core/models/fitness-store.d.ts.map +1 -1
- package/dist/core/models/fitness-store.js +1 -1
- package/dist/core/models/fitness-store.js.map +1 -1
- package/dist/core/models/local-runtime.d.ts +6 -4
- package/dist/core/models/local-runtime.d.ts.map +1 -1
- package/dist/core/models/local-runtime.js +108 -36
- package/dist/core/models/local-runtime.js.map +1 -1
- package/dist/core/output-guard.d.ts.map +1 -1
- package/dist/core/output-guard.js +23 -8
- package/dist/core/output-guard.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +44 -59
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/profile-resource-selection.d.ts +16 -0
- package/dist/core/profile-resource-selection.d.ts.map +1 -1
- package/dist/core/profile-resource-selection.js +41 -0
- package/dist/core/profile-resource-selection.js.map +1 -1
- package/dist/core/reflection-controller.d.ts.map +1 -1
- package/dist/core/reflection-controller.js +18 -1
- package/dist/core/reflection-controller.js.map +1 -1
- package/dist/core/reload-blockers.d.ts +1 -0
- package/dist/core/reload-blockers.d.ts.map +1 -1
- package/dist/core/reload-blockers.js +6 -2
- package/dist/core/reload-blockers.js.map +1 -1
- package/dist/core/resource-loader.d.ts +2 -0
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +50 -28
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/toolkit/script-runner.d.ts.map +1 -1
- package/dist/core/toolkit/script-runner.js +58 -24
- package/dist/core/toolkit/script-runner.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +13 -26
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/extensionify.d.ts +2 -1
- package/dist/core/tools/extensionify.d.ts.map +1 -1
- package/dist/core/tools/extensionify.js +11 -8
- package/dist/core/tools/extensionify.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +54 -48
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/git-filter.d.ts.map +1 -1
- package/dist/core/tools/git-filter.js +20 -32
- package/dist/core/tools/git-filter.js.map +1 -1
- package/dist/core/tools/goal.d.ts +1 -1
- package/dist/core/tools/goal.d.ts.map +1 -1
- package/dist/core/tools/goal.js +10 -3
- package/dist/core/tools/goal.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +89 -80
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/output-accumulator.d.ts +2 -0
- package/dist/core/tools/output-accumulator.d.ts.map +1 -1
- package/dist/core/tools/output-accumulator.js +7 -4
- package/dist/core/tools/output-accumulator.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +4 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +2 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +23 -4
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +18 -4
- package/dist/migrations.js.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.d.ts +10 -0
- package/dist/modes/interactive/auth-dialogs-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auth-dialogs-controller.js +94 -27
- package/dist/modes/interactive/auth-dialogs-controller.js.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.d.ts.map +1 -1
- package/dist/modes/interactive/auto-learn-controller.js +2 -1
- package/dist/modes/interactive/auto-learn-controller.js.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.d.ts +1 -0
- package/dist/modes/interactive/components/bordered-loader.d.ts.map +1 -1
- package/dist/modes/interactive/components/bordered-loader.js +8 -0
- package/dist/modes/interactive/components/bordered-loader.js.map +1 -1
- package/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-editor.js +6 -15
- package/dist/modes/interactive/components/extension-editor.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +3 -1
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +20 -8
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts +4 -1
- package/dist/modes/interactive/components/profile-resource-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/profile-resource-editor.js +22 -39
- package/dist/modes/interactive/components/profile-resource-editor.js.map +1 -1
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +1 -1
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/config-backup.d.ts +1 -0
- package/dist/modes/interactive/config-backup.d.ts.map +1 -1
- package/dist/modes/interactive/config-backup.js +14 -3
- package/dist/modes/interactive/config-backup.js.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.d.ts +12 -0
- package/dist/modes/interactive/editor-overlay-host.d.ts.map +1 -1
- package/dist/modes/interactive/editor-overlay-host.js +55 -0
- package/dist/modes/interactive/editor-overlay-host.js.map +1 -1
- package/dist/modes/interactive/extension-ui-host.d.ts +2 -1
- package/dist/modes/interactive/extension-ui-host.d.ts.map +1 -1
- package/dist/modes/interactive/extension-ui-host.js +161 -96
- package/dist/modes/interactive/extension-ui-host.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +23 -28
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +14 -8
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts +5 -1
- package/dist/modes/interactive/profile-menu-controller.d.ts.map +1 -1
- package/dist/modes/interactive/profile-menu-controller.js +118 -24
- package/dist/modes/interactive/profile-menu-controller.js.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.d.ts +1 -0
- package/dist/modes/interactive/resource-shell-commands.d.ts.map +1 -1
- package/dist/modes/interactive/resource-shell-commands.js +5 -1
- package/dist/modes/interactive/resource-shell-commands.js.map +1 -1
- package/dist/modes/interactive/session-flow-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-flow-commands.js +100 -7
- package/dist/modes/interactive/session-flow-commands.js.map +1 -1
- package/dist/modes/interactive/session-io-commands.d.ts.map +1 -1
- package/dist/modes/interactive/session-io-commands.js +80 -50
- package/dist/modes/interactive/session-io-commands.js.map +1 -1
- package/dist/modes/interactive/startup-checks.d.ts.map +1 -1
- package/dist/modes/interactive/startup-checks.js +14 -17
- package/dist/modes/interactive/startup-checks.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +20 -19
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +10 -19
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/child-process.d.ts +13 -7
- package/dist/utils/child-process.d.ts.map +1 -1
- package/dist/utils/child-process.js +56 -3
- package/dist/utils/child-process.js.map +1 -1
- package/dist/utils/clipboard-image.d.ts.map +1 -1
- package/dist/utils/clipboard-image.js +3 -2
- package/dist/utils/clipboard-image.js.map +1 -1
- package/dist/utils/clipboard.d.ts.map +1 -1
- package/dist/utils/clipboard.js +17 -10
- package/dist/utils/clipboard.js.map +1 -1
- package/dist/utils/external-editor-command.d.ts +8 -0
- package/dist/utils/external-editor-command.d.ts.map +1 -0
- package/dist/utils/external-editor-command.js +60 -0
- package/dist/utils/external-editor-command.js.map +1 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +10 -2
- package/dist/utils/paths.js.map +1 -1
- package/dist/utils/tools-manager.d.ts.map +1 -1
- package/dist/utils/tools-manager.js +23 -10
- package/dist/utils/tools-manager.js.map +1 -1
- package/dist/utils/work-directory.d.ts +36 -0
- package/dist/utils/work-directory.d.ts.map +1 -0
- package/dist/utils/work-directory.js +462 -0
- package/dist/utils/work-directory.js.map +1 -0
- package/docs/index.md +2 -0
- package/docs/resources.md +2 -2
- package/docs/rpc.md +1 -1
- package/docs/tmux-agent-manager.md +48 -0
- package/docs/windows.md +100 -7
- package/docs/work-directory.md +41 -0
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +4 -4
|
@@ -9,6 +9,22 @@ import type { ResourceProfileFilterSettings } from "./settings-manager.ts";
|
|
|
9
9
|
* (If both allow and block specific ids are present: start from allow-or-all, then remove blocked.)
|
|
10
10
|
*/
|
|
11
11
|
export declare function decodeResourceSelection(filter: ResourceProfileFilterSettings | undefined, allIds: string[]): Set<string>;
|
|
12
|
+
export interface ResourceSelectionIdentity {
|
|
13
|
+
id: string;
|
|
14
|
+
}
|
|
15
|
+
export interface RemappedResourceSelectionFilter {
|
|
16
|
+
filter: ResourceProfileFilterSettings | undefined;
|
|
17
|
+
missingIds: Set<string>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve persisted profile patterns onto the editor's collision-safe resource ids.
|
|
21
|
+
*
|
|
22
|
+
* Runtime filters intentionally accept basenames, parent directory names, globs, and paths. The
|
|
23
|
+
* editor, however, must toggle one concrete resource at a time. This remap expands every matching
|
|
24
|
+
* pattern to the concrete ids it currently selects and preserves only genuinely missing patterns.
|
|
25
|
+
* Saving then migrates ambiguous legacy patterns (for example `index.ts`) to exact concrete ids.
|
|
26
|
+
*/
|
|
27
|
+
export declare function remapResourceSelectionFilter<T extends ResourceSelectionIdentity>(filter: ResourceProfileFilterSettings | undefined, items: readonly T[], matchesPattern: (item: T, pattern: string) => boolean): RemappedResourceSelectionFilter;
|
|
12
28
|
/**
|
|
13
29
|
* Options that let the encoder distinguish a genuine grant-all from an all-enabled snapshot of an
|
|
14
30
|
* enumerated grant (e.g. a collapsed universe where every enumerated id happens to be visible).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile-resource-selection.d.ts","sourceRoot":"","sources":["../../src/core/profile-resource-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAE3E;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,6BAA6B,GAAG,SAAS,EACjD,MAAM,EAAE,MAAM,EAAE,GACd,GAAG,CAAC,MAAM,CAAC,CAmDb;AAED;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC9C,cAAc,CAAC,EAAE,6BAA6B,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,CAAC,EAAE,8BAA8B,GACtC,6BAA6B,GAAG,SAAS,CAoC3C;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,6BAA6B,GAAG,SAAS,GAAG,eAAe,CAcxG;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CACjD,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,8BAA8B,GACtC,6BAA6B,GAAG,SAAS,CAmC3C","sourcesContent":["import type { ResourceProfileFilterSettings } from \"./settings-manager.ts\";\n\n/**\n * Decode a stored allow/block filter into the set of enabled resource ids,\n * given the full universe of ids for that kind.\n * - undefined / empty filter -> all enabled\n * - block contains \"*\" -> none enabled\n * - allow non-empty -> exactly the allow entries that exist in allIds\n * - block of specific ids (no \"*\") -> all ids except blocked ones\n * (If both allow and block specific ids are present: start from allow-or-all, then remove blocked.)\n */\nexport function decodeResourceSelection(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\tallIds: string[],\n): Set<string> {\n\t// Strict UAC: an unmentioned kind is DENIED — decoding it as \"all enabled\" would both\n\t// lie in the editor UI and, on save, convert silent denial into an explicit grant-all.\n\tif (!filter) {\n\t\treturn new Set();\n\t}\n\n\t// If block contains \"*\", nothing is enabled\n\tif (filter.block?.includes(\"*\")) {\n\t\treturn new Set();\n\t}\n\n\t// Explicit grant-all: every id enabled except explicitly blocked ones.\n\tif (filter.allow?.includes(\"*\")) {\n\t\tconst blocked = new Set(filter.block ?? []);\n\t\treturn new Set(allIds.filter((id) => !blocked.has(id)));\n\t}\n\n\t// If allow is specified, start with the allow list filtered to valid IDs\n\tif (filter.allow && filter.allow.length > 0) {\n\t\tconst allIdsSet = new Set(allIds);\n\t\tconst enabled = new Set<string>();\n\t\tfor (const id of filter.allow) {\n\t\t\tif (allIdsSet.has(id)) {\n\t\t\t\tenabled.add(id);\n\t\t\t}\n\t\t}\n\t\t// If block is also specified, remove blocked ids\n\t\tif (filter.block && filter.block.length > 0) {\n\t\t\tconst blockSet = new Set(filter.block);\n\t\t\tfor (const id of Array.from(enabled)) {\n\t\t\t\tif (blockSet.has(id)) {\n\t\t\t\t\tenabled.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn enabled;\n\t}\n\n\t// No allow specified, start with all ids\n\tconst enabled = new Set(allIds);\n\t// Remove any blocked ids\n\tif (filter.block && filter.block.length > 0) {\n\t\tconst blockSet = new Set(filter.block);\n\t\tfor (const id of allIds) {\n\t\t\tif (blockSet.has(id)) {\n\t\t\t\tenabled.delete(id);\n\t\t\t}\n\t\t}\n\t}\n\treturn enabled;\n}\n\n/**\n * Options that let the encoder distinguish a genuine grant-all from an all-enabled snapshot of an\n * enumerated grant (e.g. a collapsed universe where every enumerated id happens to be visible).\n * - originalFilter: the filter the selection was decoded from. A wildcard is preserved as a\n * wildcard; an enumerated grant is NEVER widened into one.\n * - grantAll: the caller explicitly chose \"grant all (including future)\" — force the wildcard.\n */\nexport interface EncodeResourceSelectionOptions {\n\toriginalFilter?: ResourceProfileFilterSettings;\n\tgrantAll?: boolean;\n}\n\nfunction shouldEncodeGrantAllWildcard(options?: EncodeResourceSelectionOptions): boolean {\n\tif (options?.grantAll) return true;\n\treturn options?.originalFilter?.allow?.includes(\"*\") ?? false;\n}\n\n/**\n * Encode an enabled set back into a filter.\n * - all enabled -> enumerated { allow: [...allIds] } (or { allow: [\"*\"] } only when the\n * original was already a wildcard / the caller chose grant-all)\n * - some but not all -> { allow: [...enabled, in allIds order] }\n * - none enabled -> { block: [\"*\"] }\n */\nexport function encodeResourceSelection(\n\tenabled: Set<string>,\n\tallIds: string[],\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tconst allIdsSet = new Set(allIds);\n\n\t// Count how many enabled ids are actually in allIds\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: grant-all must be said OUT LOUD under strict UAC — omitting the kind (returning\n\t// undefined) would mean deny-all. But saying it out loud does NOT mean widening: an enumerated\n\t// closed grant re-encodes as the explicit id list, so a no-change save over a collapsed universe\n\t// cannot silently turn \"grant exactly these\" into \"grant everything, including future resources\".\n\t// A wildcard is only produced when it was already a wildcard (or grant-all was chosen explicitly).\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Some but not all: return { allow: [...enabled, in allIds order] }\n\tconst allow: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (enabled.has(id)) {\n\t\t\tallow.push(id);\n\t\t}\n\t}\n\treturn { allow };\n}\n\nexport type ResourceFraming = \"allow\" | \"block\";\n\n/**\n * Detect the framing of a given resource profile filter settings.\n */\nexport function detectResourceFraming(filter: ResourceProfileFilterSettings | undefined): ResourceFraming {\n\tif (!filter) {\n\t\treturn \"block\";\n\t}\n\tif (filter.allow && filter.allow.length > 0) {\n\t\treturn \"allow\";\n\t}\n\tif (filter.block && filter.block.length > 0) {\n\t\tif (filter.block.includes(\"*\")) {\n\t\t\treturn \"allow\";\n\t\t}\n\t\treturn \"block\";\n\t}\n\treturn \"block\";\n}\n\n/**\n * Encode an enabled set back into a filter, respecting the specified framing.\n */\nexport function encodeResourceSelectionWithFraming(\n\tenabled: Set<string>,\n\tallIds: string[],\n\tframing: ResourceFraming,\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tif (framing === \"allow\") {\n\t\treturn encodeResourceSelection(enabled, allIds, options);\n\t}\n\n\tconst allIdsSet = new Set(allIds);\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: explicit grant-all (see encodeResourceSelection — undefined means DENIED), but\n\t// never widened to a wildcard without context — same gate as the allow-framing branch.\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Block framing with subset disabled: list disabled ids in block\n\tconst block: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (!enabled.has(id)) {\n\t\t\tblock.push(id);\n\t\t}\n\t}\n\treturn { block };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"profile-resource-selection.d.ts","sourceRoot":"","sources":["../../src/core/profile-resource-selection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,uBAAuB,CAAC;AAE3E;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACtC,MAAM,EAAE,6BAA6B,GAAG,SAAS,EACjD,MAAM,EAAE,MAAM,EAAE,GACd,GAAG,CAAC,MAAM,CAAC,CAmDb;AAED,MAAM,WAAW,yBAAyB;IACzC,EAAE,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,+BAA+B;IAC/C,MAAM,EAAE,6BAA6B,GAAG,SAAS,CAAC;IAClD,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,CAAC,SAAS,yBAAyB,EAC/E,MAAM,EAAE,6BAA6B,GAAG,SAAS,EACjD,KAAK,EAAE,SAAS,CAAC,EAAE,EACnB,cAAc,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,GACnD,+BAA+B,CA8BjC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,8BAA8B;IAC9C,cAAc,CAAC,EAAE,6BAA6B,CAAC;IAC/C,QAAQ,CAAC,EAAE,OAAO,CAAC;CACnB;AAOD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACtC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,CAAC,EAAE,8BAA8B,GACtC,6BAA6B,GAAG,SAAS,CAoC3C;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,6BAA6B,GAAG,SAAS,GAAG,eAAe,CAcxG;AAED;;GAEG;AACH,wBAAgB,kCAAkC,CACjD,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EACpB,MAAM,EAAE,MAAM,EAAE,EAChB,OAAO,EAAE,eAAe,EACxB,OAAO,CAAC,EAAE,8BAA8B,GACtC,6BAA6B,GAAG,SAAS,CAmC3C","sourcesContent":["import type { ResourceProfileFilterSettings } from \"./settings-manager.ts\";\n\n/**\n * Decode a stored allow/block filter into the set of enabled resource ids,\n * given the full universe of ids for that kind.\n * - undefined / empty filter -> all enabled\n * - block contains \"*\" -> none enabled\n * - allow non-empty -> exactly the allow entries that exist in allIds\n * - block of specific ids (no \"*\") -> all ids except blocked ones\n * (If both allow and block specific ids are present: start from allow-or-all, then remove blocked.)\n */\nexport function decodeResourceSelection(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\tallIds: string[],\n): Set<string> {\n\t// Strict UAC: an unmentioned kind is DENIED — decoding it as \"all enabled\" would both\n\t// lie in the editor UI and, on save, convert silent denial into an explicit grant-all.\n\tif (!filter) {\n\t\treturn new Set();\n\t}\n\n\t// If block contains \"*\", nothing is enabled\n\tif (filter.block?.includes(\"*\")) {\n\t\treturn new Set();\n\t}\n\n\t// Explicit grant-all: every id enabled except explicitly blocked ones.\n\tif (filter.allow?.includes(\"*\")) {\n\t\tconst blocked = new Set(filter.block ?? []);\n\t\treturn new Set(allIds.filter((id) => !blocked.has(id)));\n\t}\n\n\t// If allow is specified, start with the allow list filtered to valid IDs\n\tif (filter.allow && filter.allow.length > 0) {\n\t\tconst allIdsSet = new Set(allIds);\n\t\tconst enabled = new Set<string>();\n\t\tfor (const id of filter.allow) {\n\t\t\tif (allIdsSet.has(id)) {\n\t\t\t\tenabled.add(id);\n\t\t\t}\n\t\t}\n\t\t// If block is also specified, remove blocked ids\n\t\tif (filter.block && filter.block.length > 0) {\n\t\t\tconst blockSet = new Set(filter.block);\n\t\t\tfor (const id of Array.from(enabled)) {\n\t\t\t\tif (blockSet.has(id)) {\n\t\t\t\t\tenabled.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn enabled;\n\t}\n\n\t// No allow specified, start with all ids\n\tconst enabled = new Set(allIds);\n\t// Remove any blocked ids\n\tif (filter.block && filter.block.length > 0) {\n\t\tconst blockSet = new Set(filter.block);\n\t\tfor (const id of allIds) {\n\t\t\tif (blockSet.has(id)) {\n\t\t\t\tenabled.delete(id);\n\t\t\t}\n\t\t}\n\t}\n\treturn enabled;\n}\n\nexport interface ResourceSelectionIdentity {\n\tid: string;\n}\n\nexport interface RemappedResourceSelectionFilter {\n\tfilter: ResourceProfileFilterSettings | undefined;\n\tmissingIds: Set<string>;\n}\n\n/**\n * Resolve persisted profile patterns onto the editor's collision-safe resource ids.\n *\n * Runtime filters intentionally accept basenames, parent directory names, globs, and paths. The\n * editor, however, must toggle one concrete resource at a time. This remap expands every matching\n * pattern to the concrete ids it currently selects and preserves only genuinely missing patterns.\n * Saving then migrates ambiguous legacy patterns (for example `index.ts`) to exact concrete ids.\n */\nexport function remapResourceSelectionFilter<T extends ResourceSelectionIdentity>(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\titems: readonly T[],\n\tmatchesPattern: (item: T, pattern: string) => boolean,\n): RemappedResourceSelectionFilter {\n\tif (!filter) return { filter: undefined, missingIds: new Set() };\n\n\tconst missingIds = new Set<string>();\n\tconst remapPatterns = (patterns: string[] | undefined): string[] | undefined => {\n\t\tif (patterns === undefined) return undefined;\n\t\tconst remapped: string[] = [];\n\t\tconst seen = new Set<string>();\n\t\tfor (const pattern of patterns) {\n\t\t\tconst ids =\n\t\t\t\tpattern === \"*\" ? [pattern] : items.filter((item) => matchesPattern(item, pattern)).map((item) => item.id);\n\t\t\tif (ids.length === 0) {\n\t\t\t\tmissingIds.add(pattern);\n\t\t\t\tids.push(pattern);\n\t\t\t}\n\t\t\tfor (const id of ids) {\n\t\t\t\tif (seen.has(id)) continue;\n\t\t\t\tseen.add(id);\n\t\t\t\tremapped.push(id);\n\t\t\t}\n\t\t}\n\t\treturn remapped;\n\t};\n\n\tconst remapped: ResourceProfileFilterSettings = {};\n\tconst allow = remapPatterns(filter.allow);\n\tconst block = remapPatterns(filter.block);\n\tif (allow !== undefined) remapped.allow = allow;\n\tif (block !== undefined) remapped.block = block;\n\treturn { filter: remapped, missingIds };\n}\n\n/**\n * Options that let the encoder distinguish a genuine grant-all from an all-enabled snapshot of an\n * enumerated grant (e.g. a collapsed universe where every enumerated id happens to be visible).\n * - originalFilter: the filter the selection was decoded from. A wildcard is preserved as a\n * wildcard; an enumerated grant is NEVER widened into one.\n * - grantAll: the caller explicitly chose \"grant all (including future)\" — force the wildcard.\n */\nexport interface EncodeResourceSelectionOptions {\n\toriginalFilter?: ResourceProfileFilterSettings;\n\tgrantAll?: boolean;\n}\n\nfunction shouldEncodeGrantAllWildcard(options?: EncodeResourceSelectionOptions): boolean {\n\tif (options?.grantAll) return true;\n\treturn options?.originalFilter?.allow?.includes(\"*\") ?? false;\n}\n\n/**\n * Encode an enabled set back into a filter.\n * - all enabled -> enumerated { allow: [...allIds] } (or { allow: [\"*\"] } only when the\n * original was already a wildcard / the caller chose grant-all)\n * - some but not all -> { allow: [...enabled, in allIds order] }\n * - none enabled -> { block: [\"*\"] }\n */\nexport function encodeResourceSelection(\n\tenabled: Set<string>,\n\tallIds: string[],\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tconst allIdsSet = new Set(allIds);\n\n\t// Count how many enabled ids are actually in allIds\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: grant-all must be said OUT LOUD under strict UAC — omitting the kind (returning\n\t// undefined) would mean deny-all. But saying it out loud does NOT mean widening: an enumerated\n\t// closed grant re-encodes as the explicit id list, so a no-change save over a collapsed universe\n\t// cannot silently turn \"grant exactly these\" into \"grant everything, including future resources\".\n\t// A wildcard is only produced when it was already a wildcard (or grant-all was chosen explicitly).\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Some but not all: return { allow: [...enabled, in allIds order] }\n\tconst allow: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (enabled.has(id)) {\n\t\t\tallow.push(id);\n\t\t}\n\t}\n\treturn { allow };\n}\n\nexport type ResourceFraming = \"allow\" | \"block\";\n\n/**\n * Detect the framing of a given resource profile filter settings.\n */\nexport function detectResourceFraming(filter: ResourceProfileFilterSettings | undefined): ResourceFraming {\n\tif (!filter) {\n\t\treturn \"block\";\n\t}\n\tif (filter.allow && filter.allow.length > 0) {\n\t\treturn \"allow\";\n\t}\n\tif (filter.block && filter.block.length > 0) {\n\t\tif (filter.block.includes(\"*\")) {\n\t\t\treturn \"allow\";\n\t\t}\n\t\treturn \"block\";\n\t}\n\treturn \"block\";\n}\n\n/**\n * Encode an enabled set back into a filter, respecting the specified framing.\n */\nexport function encodeResourceSelectionWithFraming(\n\tenabled: Set<string>,\n\tallIds: string[],\n\tframing: ResourceFraming,\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tif (framing === \"allow\") {\n\t\treturn encodeResourceSelection(enabled, allIds, options);\n\t}\n\n\tconst allIdsSet = new Set(allIds);\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: explicit grant-all (see encodeResourceSelection — undefined means DENIED), but\n\t// never widened to a wildcard without context — same gate as the allow-framing branch.\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Block framing with subset disabled: list disabled ids in block\n\tconst block: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (!enabled.has(id)) {\n\t\t\tblock.push(id);\n\t\t}\n\t}\n\treturn { block };\n}\n"]}
|
|
@@ -55,6 +55,47 @@ export function decodeResourceSelection(filter, allIds) {
|
|
|
55
55
|
}
|
|
56
56
|
return enabled;
|
|
57
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve persisted profile patterns onto the editor's collision-safe resource ids.
|
|
60
|
+
*
|
|
61
|
+
* Runtime filters intentionally accept basenames, parent directory names, globs, and paths. The
|
|
62
|
+
* editor, however, must toggle one concrete resource at a time. This remap expands every matching
|
|
63
|
+
* pattern to the concrete ids it currently selects and preserves only genuinely missing patterns.
|
|
64
|
+
* Saving then migrates ambiguous legacy patterns (for example `index.ts`) to exact concrete ids.
|
|
65
|
+
*/
|
|
66
|
+
export function remapResourceSelectionFilter(filter, items, matchesPattern) {
|
|
67
|
+
if (!filter)
|
|
68
|
+
return { filter: undefined, missingIds: new Set() };
|
|
69
|
+
const missingIds = new Set();
|
|
70
|
+
const remapPatterns = (patterns) => {
|
|
71
|
+
if (patterns === undefined)
|
|
72
|
+
return undefined;
|
|
73
|
+
const remapped = [];
|
|
74
|
+
const seen = new Set();
|
|
75
|
+
for (const pattern of patterns) {
|
|
76
|
+
const ids = pattern === "*" ? [pattern] : items.filter((item) => matchesPattern(item, pattern)).map((item) => item.id);
|
|
77
|
+
if (ids.length === 0) {
|
|
78
|
+
missingIds.add(pattern);
|
|
79
|
+
ids.push(pattern);
|
|
80
|
+
}
|
|
81
|
+
for (const id of ids) {
|
|
82
|
+
if (seen.has(id))
|
|
83
|
+
continue;
|
|
84
|
+
seen.add(id);
|
|
85
|
+
remapped.push(id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return remapped;
|
|
89
|
+
};
|
|
90
|
+
const remapped = {};
|
|
91
|
+
const allow = remapPatterns(filter.allow);
|
|
92
|
+
const block = remapPatterns(filter.block);
|
|
93
|
+
if (allow !== undefined)
|
|
94
|
+
remapped.allow = allow;
|
|
95
|
+
if (block !== undefined)
|
|
96
|
+
remapped.block = block;
|
|
97
|
+
return { filter: remapped, missingIds };
|
|
98
|
+
}
|
|
58
99
|
function shouldEncodeGrantAllWildcard(options) {
|
|
59
100
|
if (options?.grantAll)
|
|
60
101
|
return true;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile-resource-selection.js","sourceRoot":"","sources":["../../src/core/profile-resource-selection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ResourceProfileFilterSettings } from \"./settings-manager.ts\";\n\n/**\n * Decode a stored allow/block filter into the set of enabled resource ids,\n * given the full universe of ids for that kind.\n * - undefined / empty filter -> all enabled\n * - block contains \"*\" -> none enabled\n * - allow non-empty -> exactly the allow entries that exist in allIds\n * - block of specific ids (no \"*\") -> all ids except blocked ones\n * (If both allow and block specific ids are present: start from allow-or-all, then remove blocked.)\n */\nexport function decodeResourceSelection(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\tallIds: string[],\n): Set<string> {\n\t// Strict UAC: an unmentioned kind is DENIED — decoding it as \"all enabled\" would both\n\t// lie in the editor UI and, on save, convert silent denial into an explicit grant-all.\n\tif (!filter) {\n\t\treturn new Set();\n\t}\n\n\t// If block contains \"*\", nothing is enabled\n\tif (filter.block?.includes(\"*\")) {\n\t\treturn new Set();\n\t}\n\n\t// Explicit grant-all: every id enabled except explicitly blocked ones.\n\tif (filter.allow?.includes(\"*\")) {\n\t\tconst blocked = new Set(filter.block ?? []);\n\t\treturn new Set(allIds.filter((id) => !blocked.has(id)));\n\t}\n\n\t// If allow is specified, start with the allow list filtered to valid IDs\n\tif (filter.allow && filter.allow.length > 0) {\n\t\tconst allIdsSet = new Set(allIds);\n\t\tconst enabled = new Set<string>();\n\t\tfor (const id of filter.allow) {\n\t\t\tif (allIdsSet.has(id)) {\n\t\t\t\tenabled.add(id);\n\t\t\t}\n\t\t}\n\t\t// If block is also specified, remove blocked ids\n\t\tif (filter.block && filter.block.length > 0) {\n\t\t\tconst blockSet = new Set(filter.block);\n\t\t\tfor (const id of Array.from(enabled)) {\n\t\t\t\tif (blockSet.has(id)) {\n\t\t\t\t\tenabled.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn enabled;\n\t}\n\n\t// No allow specified, start with all ids\n\tconst enabled = new Set(allIds);\n\t// Remove any blocked ids\n\tif (filter.block && filter.block.length > 0) {\n\t\tconst blockSet = new Set(filter.block);\n\t\tfor (const id of allIds) {\n\t\t\tif (blockSet.has(id)) {\n\t\t\t\tenabled.delete(id);\n\t\t\t}\n\t\t}\n\t}\n\treturn enabled;\n}\n\n/**\n * Options that let the encoder distinguish a genuine grant-all from an all-enabled snapshot of an\n * enumerated grant (e.g. a collapsed universe where every enumerated id happens to be visible).\n * - originalFilter: the filter the selection was decoded from. A wildcard is preserved as a\n * wildcard; an enumerated grant is NEVER widened into one.\n * - grantAll: the caller explicitly chose \"grant all (including future)\" — force the wildcard.\n */\nexport interface EncodeResourceSelectionOptions {\n\toriginalFilter?: ResourceProfileFilterSettings;\n\tgrantAll?: boolean;\n}\n\nfunction shouldEncodeGrantAllWildcard(options?: EncodeResourceSelectionOptions): boolean {\n\tif (options?.grantAll) return true;\n\treturn options?.originalFilter?.allow?.includes(\"*\") ?? false;\n}\n\n/**\n * Encode an enabled set back into a filter.\n * - all enabled -> enumerated { allow: [...allIds] } (or { allow: [\"*\"] } only when the\n * original was already a wildcard / the caller chose grant-all)\n * - some but not all -> { allow: [...enabled, in allIds order] }\n * - none enabled -> { block: [\"*\"] }\n */\nexport function encodeResourceSelection(\n\tenabled: Set<string>,\n\tallIds: string[],\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tconst allIdsSet = new Set(allIds);\n\n\t// Count how many enabled ids are actually in allIds\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: grant-all must be said OUT LOUD under strict UAC — omitting the kind (returning\n\t// undefined) would mean deny-all. But saying it out loud does NOT mean widening: an enumerated\n\t// closed grant re-encodes as the explicit id list, so a no-change save over a collapsed universe\n\t// cannot silently turn \"grant exactly these\" into \"grant everything, including future resources\".\n\t// A wildcard is only produced when it was already a wildcard (or grant-all was chosen explicitly).\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Some but not all: return { allow: [...enabled, in allIds order] }\n\tconst allow: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (enabled.has(id)) {\n\t\t\tallow.push(id);\n\t\t}\n\t}\n\treturn { allow };\n}\n\nexport type ResourceFraming = \"allow\" | \"block\";\n\n/**\n * Detect the framing of a given resource profile filter settings.\n */\nexport function detectResourceFraming(filter: ResourceProfileFilterSettings | undefined): ResourceFraming {\n\tif (!filter) {\n\t\treturn \"block\";\n\t}\n\tif (filter.allow && filter.allow.length > 0) {\n\t\treturn \"allow\";\n\t}\n\tif (filter.block && filter.block.length > 0) {\n\t\tif (filter.block.includes(\"*\")) {\n\t\t\treturn \"allow\";\n\t\t}\n\t\treturn \"block\";\n\t}\n\treturn \"block\";\n}\n\n/**\n * Encode an enabled set back into a filter, respecting the specified framing.\n */\nexport function encodeResourceSelectionWithFraming(\n\tenabled: Set<string>,\n\tallIds: string[],\n\tframing: ResourceFraming,\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tif (framing === \"allow\") {\n\t\treturn encodeResourceSelection(enabled, allIds, options);\n\t}\n\n\tconst allIdsSet = new Set(allIds);\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: explicit grant-all (see encodeResourceSelection — undefined means DENIED), but\n\t// never widened to a wildcard without context — same gate as the allow-framing branch.\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Block framing with subset disabled: list disabled ids in block\n\tconst block: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (!enabled.has(id)) {\n\t\t\tblock.push(id);\n\t\t}\n\t}\n\treturn { block };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"profile-resource-selection.js","sourceRoot":"","sources":["../../src/core/profile-resource-selection.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ResourceProfileFilterSettings } from \"./settings-manager.ts\";\n\n/**\n * Decode a stored allow/block filter into the set of enabled resource ids,\n * given the full universe of ids for that kind.\n * - undefined / empty filter -> all enabled\n * - block contains \"*\" -> none enabled\n * - allow non-empty -> exactly the allow entries that exist in allIds\n * - block of specific ids (no \"*\") -> all ids except blocked ones\n * (If both allow and block specific ids are present: start from allow-or-all, then remove blocked.)\n */\nexport function decodeResourceSelection(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\tallIds: string[],\n): Set<string> {\n\t// Strict UAC: an unmentioned kind is DENIED — decoding it as \"all enabled\" would both\n\t// lie in the editor UI and, on save, convert silent denial into an explicit grant-all.\n\tif (!filter) {\n\t\treturn new Set();\n\t}\n\n\t// If block contains \"*\", nothing is enabled\n\tif (filter.block?.includes(\"*\")) {\n\t\treturn new Set();\n\t}\n\n\t// Explicit grant-all: every id enabled except explicitly blocked ones.\n\tif (filter.allow?.includes(\"*\")) {\n\t\tconst blocked = new Set(filter.block ?? []);\n\t\treturn new Set(allIds.filter((id) => !blocked.has(id)));\n\t}\n\n\t// If allow is specified, start with the allow list filtered to valid IDs\n\tif (filter.allow && filter.allow.length > 0) {\n\t\tconst allIdsSet = new Set(allIds);\n\t\tconst enabled = new Set<string>();\n\t\tfor (const id of filter.allow) {\n\t\t\tif (allIdsSet.has(id)) {\n\t\t\t\tenabled.add(id);\n\t\t\t}\n\t\t}\n\t\t// If block is also specified, remove blocked ids\n\t\tif (filter.block && filter.block.length > 0) {\n\t\t\tconst blockSet = new Set(filter.block);\n\t\t\tfor (const id of Array.from(enabled)) {\n\t\t\t\tif (blockSet.has(id)) {\n\t\t\t\t\tenabled.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn enabled;\n\t}\n\n\t// No allow specified, start with all ids\n\tconst enabled = new Set(allIds);\n\t// Remove any blocked ids\n\tif (filter.block && filter.block.length > 0) {\n\t\tconst blockSet = new Set(filter.block);\n\t\tfor (const id of allIds) {\n\t\t\tif (blockSet.has(id)) {\n\t\t\t\tenabled.delete(id);\n\t\t\t}\n\t\t}\n\t}\n\treturn enabled;\n}\n\nexport interface ResourceSelectionIdentity {\n\tid: string;\n}\n\nexport interface RemappedResourceSelectionFilter {\n\tfilter: ResourceProfileFilterSettings | undefined;\n\tmissingIds: Set<string>;\n}\n\n/**\n * Resolve persisted profile patterns onto the editor's collision-safe resource ids.\n *\n * Runtime filters intentionally accept basenames, parent directory names, globs, and paths. The\n * editor, however, must toggle one concrete resource at a time. This remap expands every matching\n * pattern to the concrete ids it currently selects and preserves only genuinely missing patterns.\n * Saving then migrates ambiguous legacy patterns (for example `index.ts`) to exact concrete ids.\n */\nexport function remapResourceSelectionFilter<T extends ResourceSelectionIdentity>(\n\tfilter: ResourceProfileFilterSettings | undefined,\n\titems: readonly T[],\n\tmatchesPattern: (item: T, pattern: string) => boolean,\n): RemappedResourceSelectionFilter {\n\tif (!filter) return { filter: undefined, missingIds: new Set() };\n\n\tconst missingIds = new Set<string>();\n\tconst remapPatterns = (patterns: string[] | undefined): string[] | undefined => {\n\t\tif (patterns === undefined) return undefined;\n\t\tconst remapped: string[] = [];\n\t\tconst seen = new Set<string>();\n\t\tfor (const pattern of patterns) {\n\t\t\tconst ids =\n\t\t\t\tpattern === \"*\" ? [pattern] : items.filter((item) => matchesPattern(item, pattern)).map((item) => item.id);\n\t\t\tif (ids.length === 0) {\n\t\t\t\tmissingIds.add(pattern);\n\t\t\t\tids.push(pattern);\n\t\t\t}\n\t\t\tfor (const id of ids) {\n\t\t\t\tif (seen.has(id)) continue;\n\t\t\t\tseen.add(id);\n\t\t\t\tremapped.push(id);\n\t\t\t}\n\t\t}\n\t\treturn remapped;\n\t};\n\n\tconst remapped: ResourceProfileFilterSettings = {};\n\tconst allow = remapPatterns(filter.allow);\n\tconst block = remapPatterns(filter.block);\n\tif (allow !== undefined) remapped.allow = allow;\n\tif (block !== undefined) remapped.block = block;\n\treturn { filter: remapped, missingIds };\n}\n\n/**\n * Options that let the encoder distinguish a genuine grant-all from an all-enabled snapshot of an\n * enumerated grant (e.g. a collapsed universe where every enumerated id happens to be visible).\n * - originalFilter: the filter the selection was decoded from. A wildcard is preserved as a\n * wildcard; an enumerated grant is NEVER widened into one.\n * - grantAll: the caller explicitly chose \"grant all (including future)\" — force the wildcard.\n */\nexport interface EncodeResourceSelectionOptions {\n\toriginalFilter?: ResourceProfileFilterSettings;\n\tgrantAll?: boolean;\n}\n\nfunction shouldEncodeGrantAllWildcard(options?: EncodeResourceSelectionOptions): boolean {\n\tif (options?.grantAll) return true;\n\treturn options?.originalFilter?.allow?.includes(\"*\") ?? false;\n}\n\n/**\n * Encode an enabled set back into a filter.\n * - all enabled -> enumerated { allow: [...allIds] } (or { allow: [\"*\"] } only when the\n * original was already a wildcard / the caller chose grant-all)\n * - some but not all -> { allow: [...enabled, in allIds order] }\n * - none enabled -> { block: [\"*\"] }\n */\nexport function encodeResourceSelection(\n\tenabled: Set<string>,\n\tallIds: string[],\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tconst allIdsSet = new Set(allIds);\n\n\t// Count how many enabled ids are actually in allIds\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: grant-all must be said OUT LOUD under strict UAC — omitting the kind (returning\n\t// undefined) would mean deny-all. But saying it out loud does NOT mean widening: an enumerated\n\t// closed grant re-encodes as the explicit id list, so a no-change save over a collapsed universe\n\t// cannot silently turn \"grant exactly these\" into \"grant everything, including future resources\".\n\t// A wildcard is only produced when it was already a wildcard (or grant-all was chosen explicitly).\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Some but not all: return { allow: [...enabled, in allIds order] }\n\tconst allow: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (enabled.has(id)) {\n\t\t\tallow.push(id);\n\t\t}\n\t}\n\treturn { allow };\n}\n\nexport type ResourceFraming = \"allow\" | \"block\";\n\n/**\n * Detect the framing of a given resource profile filter settings.\n */\nexport function detectResourceFraming(filter: ResourceProfileFilterSettings | undefined): ResourceFraming {\n\tif (!filter) {\n\t\treturn \"block\";\n\t}\n\tif (filter.allow && filter.allow.length > 0) {\n\t\treturn \"allow\";\n\t}\n\tif (filter.block && filter.block.length > 0) {\n\t\tif (filter.block.includes(\"*\")) {\n\t\t\treturn \"allow\";\n\t\t}\n\t\treturn \"block\";\n\t}\n\treturn \"block\";\n}\n\n/**\n * Encode an enabled set back into a filter, respecting the specified framing.\n */\nexport function encodeResourceSelectionWithFraming(\n\tenabled: Set<string>,\n\tallIds: string[],\n\tframing: ResourceFraming,\n\toptions?: EncodeResourceSelectionOptions,\n): ResourceProfileFilterSettings | undefined {\n\tif (framing === \"allow\") {\n\t\treturn encodeResourceSelection(enabled, allIds, options);\n\t}\n\n\tconst allIdsSet = new Set(allIds);\n\tlet validEnabledCount = 0;\n\tfor (const id of enabled) {\n\t\tif (allIdsSet.has(id)) {\n\t\t\tvalidEnabledCount++;\n\t\t}\n\t}\n\n\t// All enabled: explicit grant-all (see encodeResourceSelection — undefined means DENIED), but\n\t// never widened to a wildcard without context — same gate as the allow-framing branch.\n\tif (allIds.length > 0 && validEnabledCount === allIds.length) {\n\t\treturn shouldEncodeGrantAllWildcard(options) ? { allow: [\"*\"] } : { allow: [...allIds] };\n\t}\n\tif (allIds.length === 0) {\n\t\treturn undefined;\n\t}\n\n\t// None enabled: return { block: [\"*\"] }\n\tif (validEnabledCount === 0) {\n\t\treturn { block: [\"*\"] };\n\t}\n\n\t// Block framing with subset disabled: list disabled ids in block\n\tconst block: string[] = [];\n\tfor (const id of allIds) {\n\t\tif (!enabled.has(id)) {\n\t\t\tblock.push(id);\n\t\t}\n\t}\n\treturn { block };\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection-controller.d.ts","sourceRoot":"","sources":["../../src/core/reflection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Native reflection + learning-write controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection\n * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that\n * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session\n * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or\n * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live\n * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the\n * whole AgentSession.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype Agent,\n\ttype AgentContext,\n\ttype AgentLoopConfig,\n\trunAgentLoop,\n\ttype ThinkingLevel,\n} from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Api, AssistantMessage, Context, Model, SimpleStreamOptions, TextContent, Usage } from \"@caupulican/pi-ai\";\nimport type { IsolatedCompletionOptions, IsolatedCompletionResult } from \"./agent-session.ts\";\nimport type { LearningDecision } from \"./autonomy/contracts.ts\";\nimport { AUTONOMY_TELEMETRY_EVENT_TYPES, type AutonomyTelemetryEvent } from \"./autonomy/telemetry-events.ts\";\nimport {\n\tAPPLY_WRITE_REFUSED_REASON_CODE,\n\tappendLearningAuditSnapshot,\n\tcontradictionsForReflectionWrite,\n\tgetLearningAuditSnapshots,\n\ttype LearningAuditRecord,\n\tproposalFromReflectionWrite,\n\trollbackPlanForReflectionWrite,\n} from \"./learning/learning-audit.ts\";\nimport { evaluateLearningDecision } from \"./learning/learning-gate.ts\";\nimport { ObservationStore, observationKey } from \"./learning/observation-store.ts\";\nimport {\n\ttype DemandSignals,\n\tdecideDemand,\n\tReflectionEngine,\n\ttype ReflectionResult,\n\ttype ReflectionWrite,\n} from \"./learning/reflection-engine.ts\";\nimport type { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface ReflectionControllerDeps {\n\t/** Current session model (fallback for an isolated call that omits its own model). */\n\tgetModel(): Model<any> | undefined;\n\t/** The underlying agent — its `streamFn` runs the isolated completion. */\n\tgetAgent(): Agent;\n\t/** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */\n\tisRawStreamSimple(): boolean;\n\t/** Model registry for API-key/header resolution on the raw-provider path. */\n\tgetModelRegistry(): ModelRegistry;\n\t/** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */\n\tgetMemoryManager(): MemoryManager;\n\t/** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Session log — audit snapshots and learning-audit reads go through this. */\n\tgetSessionManager(): SessionManager;\n\t/** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */\n\tgetAgentDir(): string;\n\t/** Child sessions must not learn — the pass returns null for them. */\n\tisChildSession(): boolean;\n\t/** Disposal short-circuits: no completion, no writes against a dead session. */\n\tisDisposed(): boolean;\n\t/** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */\n\tgetReflectionSignal(): AbortSignal;\n\t/** Archive a promoted skill (rollback of a `promote_skill` write). */\n\tarchivePromotedSkill(name: string): boolean;\n\t/** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */\n\temitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;\n\t/** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined;\n\t/** Persist a learning-gate decision snapshot to the session log. */\n\tsaveLearningDecisionSnapshot(decision: LearningDecision): string;\n\t/** Resolve text-tool fallback for the selected isolated model, not the foreground model. */\n\tresolveTextToolCallProtocol(model: Model<Api>): SimpleStreamOptions[\"textToolCallProtocol\"];\n\t/** Ensure a managed-local model is running/resident before any isolated lane calls it. */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class ReflectionController {\n\tprivate readonly deps: ReflectionControllerDeps;\n\n\tconstructor(deps: ReflectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Run an LLM completion fully ISOLATED from the main session — the load-bearing primitive for\n\t * reflection and bounded child lanes (adaptive-agent design §6c/§7).\n\t *\n\t * Isolation invariants (audited by codex): builds fresh context (no main history), defaults to no\n\t * tools, and passes **no `sessionId`**. A tool-enabled call receives only caller-owned tools and\n\t * hooks, and is turn-bounded. It cannot mutate `agent.state.messages`, append session entries, or\n\t * touch the foreground tool registry. Mirrors `generateSummary()`'s one-shot mechanics otherwise.\n\t *\n\t * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection\n\t * microtask — decide whether to act); it does not throw on a model-level error.\n\t */\n\tasync runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult> {\n\t\tconst model = opts.model ?? this.deps.getModel();\n\t\tif (!model) {\n\t\t\tthrow new Error(\"runIsolatedCompletion: no model available\");\n\t\t}\n\t\tawait this.deps.ensureModelReady(model);\n\t\tconst thinkingLevel = opts.thinkingLevel ?? \"off\";\n\n\t\t// Fresh, isolated context: explicit messages, caller-owned tools only, nothing from the main session.\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\tmessages: opts.messages,\n\t\t\ttools: opts.tools ?? [],\n\t\t};\n\n\t\t// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing\n\t\t// can entangle this call with the main session.\n\t\tconst options: SimpleStreamOptions = {\n\t\t\tmaxTokens: opts.maxTokens,\n\t\t\tsignal: opts.signal,\n\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\treasoning: thinkingLevel,\n\t\t};\n\n\t\t// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.\n\t\t// Throw only when auth genuinely fails — providers that authenticate without an API key\n\t\t// (OAuth, local no-key) legitimately return ok with an undefined apiKey.\n\t\tif (this.deps.isRawStreamSimple()) {\n\t\t\tconst auth = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\toptions.apiKey = auth.apiKey;\n\t\t\toptions.headers = auth.headers;\n\t\t}\n\n\t\tif (opts.tools && opts.tools.length > 0) {\n\t\t\tconst agent = this.deps.getAgent();\n\t\t\tconst foregroundModel = agent.state.model;\n\t\t\tconst usesForegroundModel = foregroundModel?.provider === model.provider && foregroundModel.id === model.id;\n\t\t\tconst textToolCallProtocol = this.deps.resolveTextToolCallProtocol(model);\n\t\t\tconst requestedMaxTurns =\n\t\t\t\ttypeof opts.maxTurns === \"number\" && Number.isFinite(opts.maxTurns) ? Math.floor(opts.maxTurns) : 6;\n\t\t\tconst maxTurns = Math.max(1, Math.min(12, requestedMaxTurns));\n\t\t\tlet completedTurns = 0;\n\t\t\tconst childContext: AgentContext = {\n\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\tmessages: [],\n\t\t\t\ttools: [...opts.tools],\n\t\t\t};\n\t\t\tconst loopConfig: AgentLoopConfig = {\n\t\t\t\tmodel,\n\t\t\t\tmaxTokens: opts.maxTokens,\n\t\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\t\treasoning: thinkingLevel,\n\t\t\t\t...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n\t\t\t\t...(options.headers !== undefined ? { headers: options.headers } : {}),\n\t\t\t\ttemperature: textToolCallProtocol ? 0 : undefined,\n\t\t\t\ttextToolCallProtocol,\n\t\t\t\tonTextToolProtocolParse: usesForegroundModel ? agent.onTextToolProtocolParse : undefined,\n\t\t\t\ttransport: agent.transport,\n\t\t\t\tthinkingBudgets: agent.thinkingBudgets,\n\t\t\t\tmaxRetryDelayMs: agent.maxRetryDelayMs,\n\t\t\t\tmaxStallTurns: Math.max(2, Math.min(maxTurns, agent.maxStallTurns ?? maxTurns)),\n\t\t\t\ttoolExecution: \"sequential\",\n\t\t\t\ttoolArgumentTeachEnabled: agent.toolArgumentTeachEnabled,\n\t\t\t\tonToolArgumentValidation: usesForegroundModel ? agent.onToolArgumentValidation : undefined,\n\t\t\t\ttoolValidationEscalationThreshold: agent.toolValidationEscalationThreshold,\n\t\t\t\tonToolValidationEscalation: usesForegroundModel ? agent.onToolValidationEscalation : undefined,\n\t\t\t\tbeforeToolCall: opts.beforeToolCall,\n\t\t\t\tafterToolCall: opts.afterToolCall,\n\t\t\t\tshouldStopAfterTurn: () => {\n\t\t\t\t\tcompletedTurns += 1;\n\t\t\t\t\treturn completedTurns >= maxTurns;\n\t\t\t\t},\n\t\t\t\tconvertToLlm: agent.convertToLlm,\n\t\t\t};\n\t\t\tconst messages = await runAgentLoop(\n\t\t\t\topts.messages,\n\t\t\t\tchildContext,\n\t\t\t\tloopConfig,\n\t\t\t\t() => {},\n\t\t\t\topts.signal,\n\t\t\t\tagent.streamFn,\n\t\t\t);\n\t\t\tconst assistantMessages = messages.filter(\n\t\t\t\t(message): message is AssistantMessage => message.role === \"assistant\",\n\t\t\t);\n\t\t\tconst finalAssistant = assistantMessages.at(-1);\n\t\t\tif (!finalAssistant) {\n\t\t\t\tthrow new Error(\"runIsolatedCompletion: child loop produced no assistant message\");\n\t\t\t}\n\t\t\tconst usage = assistantMessages.reduce<Usage>(\n\t\t\t\t(total, message) => ({\n\t\t\t\t\tinput: total.input + message.usage.input,\n\t\t\t\t\toutput: total.output + message.usage.output,\n\t\t\t\t\tcacheRead: total.cacheRead + message.usage.cacheRead,\n\t\t\t\t\tcacheWrite: total.cacheWrite + message.usage.cacheWrite,\n\t\t\t\t\ttotalTokens: total.totalTokens + message.usage.totalTokens,\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: total.cost.input + message.usage.cost.input,\n\t\t\t\t\t\toutput: total.cost.output + message.usage.cost.output,\n\t\t\t\t\t\tcacheRead: total.cost.cacheRead + message.usage.cost.cacheRead,\n\t\t\t\t\t\tcacheWrite: total.cost.cacheWrite + message.usage.cost.cacheWrite,\n\t\t\t\t\t\ttotal: total.cost.total + message.usage.cost.total,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst text = finalAssistant.content\n\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t.map((content) => content.text)\n\t\t\t\t.join(\"\");\n\t\t\treturn { text, usage, stopReason: finalAssistant.stopReason };\n\t\t}\n\n\t\tconst stream = await this.deps.getAgent().streamFn(model, context, options);\n\t\tconst result = await stream.result();\n\t\tconst text = result.content\n\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\");\n\t\tconst usage: Usage = result.usage ?? {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t};\n\t\treturn { text, usage, stopReason: result.stopReason };\n\t}\n\n\t/**\n\t * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the\n\t * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the\n\t * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token\n\t * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.\n\t *\n\t * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass\n\t * is best-effort: a model/parse error yields no writes, never throws into the caller.\n\t */\n\tasync runReflectionPass(input: {\n\t\tsignals: DemandSignals;\n\t\trecentTurnText: string;\n\t\tmodel?: Model<any>;\n\t\tthinkingLevel?: ThinkingLevel;\n\t\tsignal?: AbortSignal;\n\t\t/** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */\n\t\treportId?: string;\n\t}): Promise<ReflectionResult | null> {\n\t\tif (this.deps.isChildSession() || this.deps.isDisposed()) return null;\n\t\tconst plan = decideDemand(input.signals);\n\t\tif (plan.act === \"skip\") return null;\n\n\t\t// Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the\n\t\t// in-flight completion (input.signal can add a more specific abort).\n\t\tconst signal = input.signal\n\t\t\t? AbortSignal.any([input.signal, this.deps.getReflectionSignal()])\n\t\t\t: this.deps.getReflectionSignal();\n\n\t\tconst complete = (systemPrompt: string, userPrompt: string) =>\n\t\t\tthis.runIsolatedCompletion({\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: [{ role: \"user\", content: [{ type: \"text\", text: userPrompt }], timestamp: Date.now() }],\n\t\t\t\tmodel: input.model,\n\t\t\t\tthinkingLevel: input.thinkingLevel ?? \"low\",\n\t\t\t\tmaxTokens: plan.tokenBudget,\n\t\t\t\tsignal,\n\t\t\t\t// The reflection system prompt is static (#33) — let the provider cache the prefix so\n\t\t\t\t// repeated passes only pay for the variable tail.\n\t\t\t\tcacheRetention: \"short\",\n\t\t\t});\n\n\t\tconst result = await new ReflectionEngine().reflect({\n\t\t\trecentTurnText: input.recentTurnText,\n\t\t\t// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write\n\t\t\t// sees writes made earlier this session.\n\t\t\texistingMemory: this.deps.getMemoryManager().buildSystemPromptBlockFresh() || \"\",\n\t\t\tplan,\n\t\t\tcomplete,\n\t\t});\n\n\t\t// Bug #21: if the session was disposed while the completion was in flight, do NOT write memory\n\t\t// or skills against the dead session.\n\t\tif (this.deps.isDisposed()) return result;\n\n\t\t// Learning apply policy: every durable write is converted to a proposal, decided by the\n\t\t// learning gate, and audited with a rollback plan. With the policy disabled (default) the\n\t\t// legacy direct-apply behavior is preserved — but now leaves audit records with rollback info.\n\t\tconst policy = this.deps.getSettingsManager().getLearningPolicySettings();\n\t\t// The audit id sequence counts STORED snapshots only: it reseeds from the stored count on\n\t\t// every pass, so advancing it for a no-op (which stores nothing) would make later passes\n\t\t// reuse ids — and rollback keys on the id, so a collision blocks or misdirects rollback.\n\t\tlet auditSequence = getLearningAuditSnapshots(this.deps.getSessionManager().getEntries()).length;\n\t\t// G6 evidence strength: durable proposals accumulate observation counts across passes/sessions\n\t\t// so the gate can distinguish a one-off cue from a repeatedly-confirmed lesson. Built once per\n\t\t// pass; every increment is best-effort (store IO must never break reflection).\n\t\tconst observationStore = ObservationStore.forAgentDir(this.deps.getAgentDir());\n\t\tlet writeIndex = 0;\n\t\tfor (const write of result.writes) {\n\t\t\twriteIndex += 1;\n\t\t\tconst proposalId = `${input.reportId ?? \"reflection\"}-w${writeIndex}`;\n\t\t\tconst proposal = proposalFromReflectionWrite(write, proposalId);\n\t\t\tconst rollback = rollbackPlanForReflectionWrite(write);\n\t\t\tlet observations = 1;\n\t\t\tif (policy.enabled) {\n\t\t\t\ttry {\n\t\t\t\t\tobservations = observationStore.increment(observationKey(proposal.layer, proposal.summary));\n\t\t\t\t} catch {\n\t\t\t\t\t// A store read/write failure falls back to a fresh count of 1, which keeps the gate\n\t\t\t\t\t// proposal-first (never spuriously auto-applies) rather than crashing the pass.\n\t\t\t\t\tobservations = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst decision: LearningDecision = policy.enabled\n\t\t\t\t? evaluateLearningDecision({\n\t\t\t\t\t\tproposal,\n\t\t\t\t\t\tconfidence: policy.reflectionSourceConfidence,\n\t\t\t\t\t\tobservations,\n\t\t\t\t\t\t// A replace/remove supersedes an existing durable fact — the reflection engine's\n\t\t\t\t\t\t// confront-before-write conflict signal — so it routes through approval instead of\n\t\t\t\t\t\t// silently overwriting prior memory. Additive writes contradict nothing.\n\t\t\t\t\t\tcontradictions: contradictionsForReflectionWrite(write),\n\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\tautoApplyEnabled: policy.autoApplyEnabled,\n\t\t\t\t\t\t\tconfidenceThreshold: policy.confidenceThreshold,\n\t\t\t\t\t\t\tminObservations: policy.minObservations,\n\t\t\t\t\t\t\tallowedAutoApplyLayers: policy.allowedAutoApplyLayers,\n\t\t\t\t\t\t\trequireRollbackPlan: policy.requireRollbackPlan,\n\t\t\t\t\t\t\tautoApplySupersessions: policy.autoApplySupersessions,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tkind: \"apply\",\n\t\t\t\t\t\treasonCode: \"learning_policy_disabled_legacy_apply\",\n\t\t\t\t\t\tconfidence: 0,\n\t\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\t\trequiresApproval: false,\n\t\t\t\t\t};\n\n\t\t\tthis.deps.saveLearningDecisionSnapshot(decision);\n\t\t\t// G3: learning-gate outcome. Codes/numbers only — never the proposal summary/memory text.\n\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.learningDecision,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\tconfidence: decision.confidence,\n\t\t\t\t\trequiresApproval: decision.requiresApproval,\n\t\t\t\t},\n\t\t\t});\n\t\t\t// G8: a proposal that needs human sign-off is an approval REQUEST. Codes/layer only —\n\t\t\t// never the proposal summary/memory text (those live only in the audit snapshot).\n\t\t\tif (decision.requiresApproval) {\n\t\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.approvalRequest,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tpayload: {\n\t\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// The gate's decision and the write's actual outcome are two different questions: the memory\n\t\t\t// tool can refuse a write (budget exceeded, drift, threat) via details.success:false without\n\t\t\t// throwing. Capture that outcome instead of assuming \"decision.kind === apply\" means it landed\n\t\t\t// — otherwise a refused write leaves a phantom \"apply\" audit whose rollback later fails\n\t\t\t// not-found (or, worse, misfires against whatever now occupies that text).\n\t\t\tconst applied = decision.kind === \"apply\" ? await this._applyReflectionWrite(write, signal) : false;\n\t\t\tconst writeFailed = decision.kind === \"apply\" && !applied;\n\t\t\tif (decision.kind !== \"no-op\") {\n\t\t\t\tauditSequence += 1;\n\t\t\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\t\t\tid: `audit-${auditSequence}`,\n\t\t\t\t\tproposalId,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\taction: writeFailed ? \"apply_failed\" : decision.kind === \"apply\" ? \"apply\" : \"propose\",\n\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\treasonCode: writeFailed ? APPLY_WRITE_REFUSED_REASON_CODE : decision.reasonCode,\n\t\t\t\t\tdecision,\n\t\t\t\t\t// No rollback plan on a failed apply — nothing durable landed, so there is nothing to undo.\n\t\t\t\t\trollback: writeFailed ? undefined : rollback,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).\n\t\t// Idempotent on reportId so a retried/duplicated pass cannot double-count.\n\t\tif (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {\n\t\t\tthis.deps.addSpawnedUsage(result.usage, { label: \"reflection\", reportId: input.reportId });\n\t\t}\n\t\treturn result;\n\t}\n\n\tgetLearningAuditRecords(): LearningAuditRecord[] {\n\t\treturn getLearningAuditSnapshots(this.deps.getSessionManager().getEntries());\n\t}\n\n\t/**\n\t * Roll back one applied durable learning change by executing the inverse operation recorded in\n\t * its audit record (memory ops run through the same bundled memory-tool path as the original\n\t * apply; promoted skills are archived). Appends a linked \"rollback\" audit record on success so\n\t * the change history stays complete and a change cannot be rolled back twice.\n\t */\n\tasync rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string }> {\n\t\tif (this.deps.isDisposed()) return { ok: false, reason: \"session_disposed\" };\n\n\t\tconst audits = this.getLearningAuditRecords();\n\t\tconst audit = audits.find((record) => record.id === auditId);\n\t\tif (!audit) return { ok: false, reason: \"audit_not_found\" };\n\t\tif (audit.action !== \"apply\") return { ok: false, reason: \"not_an_applied_change\" };\n\t\tif (audits.some((record) => record.action === \"rollback\" && record.rollbackOf === auditId)) {\n\t\t\treturn { ok: false, reason: \"already_rolled_back\" };\n\t\t}\n\t\tconst rollback = audit.rollback;\n\t\tif (!rollback) return { ok: false, reason: \"no_rollback_plan\" };\n\n\t\t// Every inverse must be VERIFIED-applied before the rollback audit is appended: a silently\n\t\t// failed inverse that still recorded \"rollback\" would permanently self-lock the change\n\t\t// behind already_rolled_back while the durable write is in fact still live.\n\t\tswitch (rollback.kind) {\n\t\t\tcase \"memory_remove\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!(await this._applyReflectionWrite({ kind: \"memory_remove\", target: rollback.target }))) {\n\t\t\t\t\treturn { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_restore\": {\n\t\t\t\tif (!rollback.target || rollback.previous === undefined) {\n\t\t\t\t\treturn { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\t}\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_replace\",\n\t\t\t\t\ttarget: rollback.target,\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_add\": {\n\t\t\t\tif (rollback.previous === undefined) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_add\",\n\t\t\t\t\tsection: \"MEMORY\",\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"archive_skill\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!this.deps.archivePromotedSkill(rollback.target)) {\n\t\t\t\t\treturn { ok: false, reason: \"skill_archive_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\tid: `${audit.id}-rollback`,\n\t\t\tproposalId: audit.proposalId,\n\t\t\tlayer: audit.layer,\n\t\t\taction: \"rollback\",\n\t\t\tsummary: `Rolled back: ${audit.summary}`,\n\t\t\treasonCode: \"user_requested_rollback\",\n\t\t\tdecision: audit.decision,\n\t\t\trollbackOf: audit.id,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t});\n\t\treturn { ok: true, reason: \"rollback_applied\" };\n\t}\n\n\t/**\n\t * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`\n\t * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring\n\t * isn't found there. Never throws (reflection must never break a turn); returns whether the write\n\t * actually applied so callers that MUST know — rollback's once-only accounting — can react instead\n\t * of recording a success that never happened.\n\t */\n\tprivate async _applyReflectionWrite(write: ReflectionWrite, signal?: AbortSignal): Promise<boolean> {\n\t\t// R7 memory-to-behavior: a recurring procedure is compiled into an executable skill file rather\n\t\t// than stored as a flat fact. Written under the agent skills dir so it loads like any user skill.\n\t\tif (write.kind === \"promote_skill\") {\n\t\t\treturn this._promoteReflectionSkill(write.name, write.description, write.body);\n\t\t}\n\n\t\ttype MemResult = { details?: { success?: boolean; error?: string } };\n\t\ttype MemExec = (\n\t\t\ttoolCallId: string,\n\t\t\tparams: { action: string; target: string; content?: string; oldContent?: string },\n\t\t\tsignal: AbortSignal | undefined,\n\t\t\tonUpdate: undefined,\n\t\t\tctx: undefined,\n\t\t) => Promise<MemResult>;\n\t\tconst memTool = this.deps\n\t\t\t.getMemoryManager()\n\t\t\t.getToolDefinitions()\n\t\t\t.find((t) => t.name === \"memory\");\n\t\tconst exec = memTool?.execute as unknown as MemExec | undefined;\n\t\tif (!exec) return false;\n\n\t\tconst run = (params: Parameters<MemExec>[1]) => exec(\"reflection\", params, signal, undefined, undefined);\n\n\t\tif (write.kind === \"memory_add\") {\n\t\t\ttry {\n\t\t\t\tconst res = await run({\n\t\t\t\t\taction: \"add\",\n\t\t\t\t\ttarget: write.section === \"USER\" ? \"user\" : \"memory\",\n\t\t\t\t\tcontent: write.text,\n\t\t\t\t});\n\t\t\t\treturn res?.details?.success === true;\n\t\t\t} catch {\n\t\t\t\t// best-effort; reflection writes must never throw into the turn loop\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports\n\t\t// outcomes via `details.success` (it catches its own errors rather than throwing). Only a\n\t\t// genuine \"not found in the file\" justifies trying the other file; a real failure for a file\n\t\t// (budget exceeded / drift) must NOT fall through and mutate the wrong target.\n\t\tfor (const target of [\"memory\", \"user\"] as const) {\n\t\t\ttry {\n\t\t\t\tconst params =\n\t\t\t\t\twrite.kind === \"memory_replace\"\n\t\t\t\t\t\t? { action: \"replace\", target, oldContent: write.target, content: write.text }\n\t\t\t\t\t\t: { action: \"remove\", target, oldContent: write.target };\n\t\t\t\tconst res = await run(params);\n\t\t\t\tif (res?.details?.success === true) return true; // applied\n\t\t\t\tif (!/not found/i.test(String(res?.details?.error ?? \"\"))) return false; // real failure — don't misapply\n\t\t\t\t// substring simply absent from this file — try the next target\n\t\t\t} catch {\n\t\t\t\t// defensive: if the tool ever does throw, try the next target\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any\n\t * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.\n\t */\n\tprivate _promoteReflectionSkill(rawName: string, description: string, body: string): boolean {\n\t\tconst name = rawName\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\")\n\t\t\t.slice(0, 64);\n\t\tif (!name || !body.trim()) return false;\n\t\ttry {\n\t\t\tconst dir = join(this.deps.getAgentDir(), \"skills\", name);\n\t\t\tconst file = join(dir, \"SKILL.md\");\n\t\t\tif (existsSync(file)) return false; // do not overwrite an existing skill\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\tconst safeDescription = description.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t\t// `promoted: true` marks this as reflection-generated so the curator (#32) can lifecycle-manage\n\t\t\t// it (archive/consolidate) WITHOUT ever touching hand-authored user skills.\n\t\t\tconst content = `---\\nname: ${name}\\ndescription: ${safeDescription}\\npromoted: true\\n---\\n\\n<!-- Auto-generated by the reflection engine (R7 memory-to-behavior). Review and refine. -->\\n\\n${body.trim()}\\n`;\n\t\t\twriteFileSync(file, content, \"utf-8\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// promotion must never break a turn\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reflection-controller.d.ts","sourceRoot":"","sources":["../../src/core/reflection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Native reflection + learning-write controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection\n * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that\n * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session\n * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or\n * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live\n * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the\n * whole AgentSession.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype Agent,\n\ttype AgentContext,\n\ttype AgentLoopConfig,\n\trunAgentLoop,\n\ttype ThinkingLevel,\n} from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Api, AssistantMessage, Context, Model, SimpleStreamOptions, TextContent, Usage } from \"@caupulican/pi-ai\";\nimport type { IsolatedCompletionOptions, IsolatedCompletionResult } from \"./agent-session.ts\";\nimport type { LearningDecision } from \"./autonomy/contracts.ts\";\nimport { AUTONOMY_TELEMETRY_EVENT_TYPES, type AutonomyTelemetryEvent } from \"./autonomy/telemetry-events.ts\";\nimport {\n\tAPPLY_WRITE_REFUSED_REASON_CODE,\n\tappendLearningAuditSnapshot,\n\tcontradictionsForReflectionWrite,\n\tgetLearningAuditSnapshots,\n\ttype LearningAuditRecord,\n\tproposalFromReflectionWrite,\n\trollbackPlanForReflectionWrite,\n} from \"./learning/learning-audit.ts\";\nimport { evaluateLearningDecision } from \"./learning/learning-gate.ts\";\nimport { ObservationStore, observationKey } from \"./learning/observation-store.ts\";\nimport {\n\ttype DemandSignals,\n\tdecideDemand,\n\tReflectionEngine,\n\ttype ReflectionResult,\n\ttype ReflectionWrite,\n} from \"./learning/reflection-engine.ts\";\nimport type { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface ReflectionControllerDeps {\n\t/** Current session model (fallback for an isolated call that omits its own model). */\n\tgetModel(): Model<any> | undefined;\n\t/** The underlying agent — its `streamFn` runs the isolated completion. */\n\tgetAgent(): Agent;\n\t/** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */\n\tisRawStreamSimple(): boolean;\n\t/** Model registry for API-key/header resolution on the raw-provider path. */\n\tgetModelRegistry(): ModelRegistry;\n\t/** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */\n\tgetMemoryManager(): MemoryManager;\n\t/** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Session log — audit snapshots and learning-audit reads go through this. */\n\tgetSessionManager(): SessionManager;\n\t/** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */\n\tgetAgentDir(): string;\n\t/** Child sessions must not learn — the pass returns null for them. */\n\tisChildSession(): boolean;\n\t/** Disposal short-circuits: no completion, no writes against a dead session. */\n\tisDisposed(): boolean;\n\t/** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */\n\tgetReflectionSignal(): AbortSignal;\n\t/** Archive a promoted skill (rollback of a `promote_skill` write). */\n\tarchivePromotedSkill(name: string): boolean;\n\t/** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */\n\temitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;\n\t/** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined;\n\t/** Persist a learning-gate decision snapshot to the session log. */\n\tsaveLearningDecisionSnapshot(decision: LearningDecision): string;\n\t/** Resolve text-tool fallback for the selected isolated model, not the foreground model. */\n\tresolveTextToolCallProtocol(model: Model<Api>): SimpleStreamOptions[\"textToolCallProtocol\"];\n\t/** Ensure a managed-local model is running/resident before any isolated lane calls it. */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class ReflectionController {\n\tprivate readonly deps: ReflectionControllerDeps;\n\n\tconstructor(deps: ReflectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Run an LLM completion fully ISOLATED from the main session — the load-bearing primitive for\n\t * reflection and bounded child lanes (adaptive-agent design §6c/§7).\n\t *\n\t * Isolation invariants (audited by codex): builds fresh context (no main history), defaults to no\n\t * tools, and passes **no `sessionId`**. A tool-enabled call receives only caller-owned tools and\n\t * hooks, and is turn-bounded. It cannot mutate `agent.state.messages`, append session entries, or\n\t * touch the foreground tool registry. Mirrors `generateSummary()`'s one-shot mechanics otherwise.\n\t *\n\t * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection\n\t * microtask — decide whether to act); it does not throw on a model-level error.\n\t */\n\tasync runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult> {\n\t\tconst model = opts.model ?? this.deps.getModel();\n\t\tif (!model) {\n\t\t\tthrow new Error(\"runIsolatedCompletion: no model available\");\n\t\t}\n\t\tawait this.deps.ensureModelReady(model);\n\t\tconst thinkingLevel = opts.thinkingLevel ?? \"off\";\n\n\t\t// Fresh, isolated context: explicit messages, caller-owned tools only, nothing from the main session.\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\tmessages: opts.messages,\n\t\t\ttools: opts.tools ?? [],\n\t\t};\n\n\t\t// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing\n\t\t// can entangle this call with the main session.\n\t\tconst options: SimpleStreamOptions = {\n\t\t\tmaxTokens: opts.maxTokens,\n\t\t\tsignal: opts.signal,\n\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\treasoning: thinkingLevel,\n\t\t};\n\n\t\t// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.\n\t\t// Throw only when auth genuinely fails — providers that authenticate without an API key\n\t\t// (OAuth, local no-key) legitimately return ok with an undefined apiKey.\n\t\tif (this.deps.isRawStreamSimple()) {\n\t\t\tconst auth = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\toptions.apiKey = auth.apiKey;\n\t\t\toptions.headers = auth.headers;\n\t\t}\n\n\t\tif (opts.tools && opts.tools.length > 0) {\n\t\t\tconst agent = this.deps.getAgent();\n\t\t\tconst foregroundModel = agent.state.model;\n\t\t\tconst usesForegroundModel = foregroundModel?.provider === model.provider && foregroundModel.id === model.id;\n\t\t\tconst textToolCallProtocol = this.deps.resolveTextToolCallProtocol(model);\n\t\t\tconst requestedMaxTurns =\n\t\t\t\ttypeof opts.maxTurns === \"number\" && Number.isFinite(opts.maxTurns) ? Math.floor(opts.maxTurns) : 6;\n\t\t\tconst maxTurns = Math.max(1, Math.min(12, requestedMaxTurns));\n\t\t\tlet completedTurns = 0;\n\t\t\tconst childContext: AgentContext = {\n\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\tmessages: [],\n\t\t\t\ttools: [...opts.tools],\n\t\t\t};\n\t\t\tconst loopConfig: AgentLoopConfig = {\n\t\t\t\tmodel,\n\t\t\t\tmaxTokens: opts.maxTokens,\n\t\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\t\treasoning: thinkingLevel,\n\t\t\t\t...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n\t\t\t\t...(options.headers !== undefined ? { headers: options.headers } : {}),\n\t\t\t\ttemperature: textToolCallProtocol ? 0 : undefined,\n\t\t\t\ttextToolCallProtocol,\n\t\t\t\tonTextToolProtocolParse: usesForegroundModel ? agent.onTextToolProtocolParse : undefined,\n\t\t\t\ttransport: agent.transport,\n\t\t\t\tthinkingBudgets: agent.thinkingBudgets,\n\t\t\t\tmaxRetryDelayMs: agent.maxRetryDelayMs,\n\t\t\t\tmaxStallTurns: Math.max(2, Math.min(maxTurns, agent.maxStallTurns ?? maxTurns)),\n\t\t\t\ttoolExecution: \"sequential\",\n\t\t\t\ttoolArgumentTeachEnabled: agent.toolArgumentTeachEnabled,\n\t\t\t\tonToolArgumentValidation: usesForegroundModel ? agent.onToolArgumentValidation : undefined,\n\t\t\t\ttoolValidationEscalationThreshold: agent.toolValidationEscalationThreshold,\n\t\t\t\tonToolValidationEscalation: usesForegroundModel ? agent.onToolValidationEscalation : undefined,\n\t\t\t\tbeforeToolCall: opts.beforeToolCall,\n\t\t\t\tafterToolCall: opts.afterToolCall,\n\t\t\t\tshouldStopAfterTurn: () => {\n\t\t\t\t\tcompletedTurns += 1;\n\t\t\t\t\treturn completedTurns >= maxTurns;\n\t\t\t\t},\n\t\t\t\tconvertToLlm: agent.convertToLlm,\n\t\t\t};\n\t\t\tconst messages = await runAgentLoop(\n\t\t\t\topts.messages,\n\t\t\t\tchildContext,\n\t\t\t\tloopConfig,\n\t\t\t\t() => {},\n\t\t\t\topts.signal,\n\t\t\t\tagent.streamFn,\n\t\t\t);\n\t\t\tconst assistantMessages = messages.filter(\n\t\t\t\t(message): message is AssistantMessage => message.role === \"assistant\",\n\t\t\t);\n\t\t\tlet finalAssistant = assistantMessages.at(-1);\n\t\t\tif (!finalAssistant) {\n\t\t\t\tthrow new Error(\"runIsolatedCompletion: child loop produced no assistant message\");\n\t\t\t}\n\n\t\t\tconst hasFinalText = finalAssistant.content.some(\n\t\t\t\t(content) => content.type === \"text\" && content.text.trim().length > 0,\n\t\t\t);\n\t\t\tconst endedOnToolCall = finalAssistant.content.some((content) => content.type === \"toolCall\");\n\t\t\tif (opts.finalTextPrompt && !hasFinalText && endedOnToolCall && !opts.signal?.aborted) {\n\t\t\t\t// A hard turn bound may stop immediately after successful tool execution. Preserve that bound,\n\t\t\t\t// then allow exactly one tool-free synthesis call so the gathered work is not thrown away.\n\t\t\t\tconst finalizationMessages = await agent.convertToLlm(messages);\n\t\t\t\tconst finalizationStream = await agent.streamFn(\n\t\t\t\t\tmodel,\n\t\t\t\t\t{\n\t\t\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\t\t\tmessages: [\n\t\t\t\t\t\t\t...finalizationMessages,\n\t\t\t\t\t\t\t{ role: \"user\", content: opts.finalTextPrompt, timestamp: Date.now() },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttools: [],\n\t\t\t\t\t},\n\t\t\t\t\toptions,\n\t\t\t\t);\n\t\t\t\tfinalAssistant = await finalizationStream.result();\n\t\t\t\tassistantMessages.push(finalAssistant);\n\t\t\t}\n\n\t\t\tconst usage = assistantMessages.reduce<Usage>(\n\t\t\t\t(total, message) => ({\n\t\t\t\t\tinput: total.input + message.usage.input,\n\t\t\t\t\toutput: total.output + message.usage.output,\n\t\t\t\t\tcacheRead: total.cacheRead + message.usage.cacheRead,\n\t\t\t\t\tcacheWrite: total.cacheWrite + message.usage.cacheWrite,\n\t\t\t\t\ttotalTokens: total.totalTokens + message.usage.totalTokens,\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: total.cost.input + message.usage.cost.input,\n\t\t\t\t\t\toutput: total.cost.output + message.usage.cost.output,\n\t\t\t\t\t\tcacheRead: total.cost.cacheRead + message.usage.cost.cacheRead,\n\t\t\t\t\t\tcacheWrite: total.cost.cacheWrite + message.usage.cost.cacheWrite,\n\t\t\t\t\t\ttotal: total.cost.total + message.usage.cost.total,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst text = finalAssistant.content\n\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t.map((content) => content.text)\n\t\t\t\t.join(\"\");\n\t\t\treturn { text, usage, stopReason: finalAssistant.stopReason };\n\t\t}\n\n\t\tconst stream = await this.deps.getAgent().streamFn(model, context, options);\n\t\tconst result = await stream.result();\n\t\tconst text = result.content\n\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\");\n\t\tconst usage: Usage = result.usage ?? {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t};\n\t\treturn { text, usage, stopReason: result.stopReason };\n\t}\n\n\t/**\n\t * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the\n\t * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the\n\t * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token\n\t * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.\n\t *\n\t * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass\n\t * is best-effort: a model/parse error yields no writes, never throws into the caller.\n\t */\n\tasync runReflectionPass(input: {\n\t\tsignals: DemandSignals;\n\t\trecentTurnText: string;\n\t\tmodel?: Model<any>;\n\t\tthinkingLevel?: ThinkingLevel;\n\t\tsignal?: AbortSignal;\n\t\t/** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */\n\t\treportId?: string;\n\t}): Promise<ReflectionResult | null> {\n\t\tif (this.deps.isChildSession() || this.deps.isDisposed()) return null;\n\t\tconst plan = decideDemand(input.signals);\n\t\tif (plan.act === \"skip\") return null;\n\n\t\t// Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the\n\t\t// in-flight completion (input.signal can add a more specific abort).\n\t\tconst signal = input.signal\n\t\t\t? AbortSignal.any([input.signal, this.deps.getReflectionSignal()])\n\t\t\t: this.deps.getReflectionSignal();\n\n\t\tconst complete = (systemPrompt: string, userPrompt: string) =>\n\t\t\tthis.runIsolatedCompletion({\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: [{ role: \"user\", content: [{ type: \"text\", text: userPrompt }], timestamp: Date.now() }],\n\t\t\t\tmodel: input.model,\n\t\t\t\tthinkingLevel: input.thinkingLevel ?? \"low\",\n\t\t\t\tmaxTokens: plan.tokenBudget,\n\t\t\t\tsignal,\n\t\t\t\t// The reflection system prompt is static (#33) — let the provider cache the prefix so\n\t\t\t\t// repeated passes only pay for the variable tail.\n\t\t\t\tcacheRetention: \"short\",\n\t\t\t});\n\n\t\tconst result = await new ReflectionEngine().reflect({\n\t\t\trecentTurnText: input.recentTurnText,\n\t\t\t// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write\n\t\t\t// sees writes made earlier this session.\n\t\t\texistingMemory: this.deps.getMemoryManager().buildSystemPromptBlockFresh() || \"\",\n\t\t\tplan,\n\t\t\tcomplete,\n\t\t});\n\n\t\t// Bug #21: if the session was disposed while the completion was in flight, do NOT write memory\n\t\t// or skills against the dead session.\n\t\tif (this.deps.isDisposed()) return result;\n\n\t\t// Learning apply policy: every durable write is converted to a proposal, decided by the\n\t\t// learning gate, and audited with a rollback plan. With the policy disabled (default) the\n\t\t// legacy direct-apply behavior is preserved — but now leaves audit records with rollback info.\n\t\tconst policy = this.deps.getSettingsManager().getLearningPolicySettings();\n\t\t// The audit id sequence counts STORED snapshots only: it reseeds from the stored count on\n\t\t// every pass, so advancing it for a no-op (which stores nothing) would make later passes\n\t\t// reuse ids — and rollback keys on the id, so a collision blocks or misdirects rollback.\n\t\tlet auditSequence = getLearningAuditSnapshots(this.deps.getSessionManager().getEntries()).length;\n\t\t// G6 evidence strength: durable proposals accumulate observation counts across passes/sessions\n\t\t// so the gate can distinguish a one-off cue from a repeatedly-confirmed lesson. Built once per\n\t\t// pass; every increment is best-effort (store IO must never break reflection).\n\t\tconst observationStore = ObservationStore.forAgentDir(this.deps.getAgentDir());\n\t\tlet writeIndex = 0;\n\t\tfor (const write of result.writes) {\n\t\t\twriteIndex += 1;\n\t\t\tconst proposalId = `${input.reportId ?? \"reflection\"}-w${writeIndex}`;\n\t\t\tconst proposal = proposalFromReflectionWrite(write, proposalId);\n\t\t\tconst rollback = rollbackPlanForReflectionWrite(write);\n\t\t\tlet observations = 1;\n\t\t\tif (policy.enabled) {\n\t\t\t\ttry {\n\t\t\t\t\tobservations = observationStore.increment(observationKey(proposal.layer, proposal.summary));\n\t\t\t\t} catch {\n\t\t\t\t\t// A store read/write failure falls back to a fresh count of 1, which keeps the gate\n\t\t\t\t\t// proposal-first (never spuriously auto-applies) rather than crashing the pass.\n\t\t\t\t\tobservations = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst decision: LearningDecision = policy.enabled\n\t\t\t\t? evaluateLearningDecision({\n\t\t\t\t\t\tproposal,\n\t\t\t\t\t\tconfidence: policy.reflectionSourceConfidence,\n\t\t\t\t\t\tobservations,\n\t\t\t\t\t\t// A replace/remove supersedes an existing durable fact — the reflection engine's\n\t\t\t\t\t\t// confront-before-write conflict signal — so it routes through approval instead of\n\t\t\t\t\t\t// silently overwriting prior memory. Additive writes contradict nothing.\n\t\t\t\t\t\tcontradictions: contradictionsForReflectionWrite(write),\n\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\tautoApplyEnabled: policy.autoApplyEnabled,\n\t\t\t\t\t\t\tconfidenceThreshold: policy.confidenceThreshold,\n\t\t\t\t\t\t\tminObservations: policy.minObservations,\n\t\t\t\t\t\t\tallowedAutoApplyLayers: policy.allowedAutoApplyLayers,\n\t\t\t\t\t\t\trequireRollbackPlan: policy.requireRollbackPlan,\n\t\t\t\t\t\t\tautoApplySupersessions: policy.autoApplySupersessions,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tkind: \"apply\",\n\t\t\t\t\t\treasonCode: \"learning_policy_disabled_legacy_apply\",\n\t\t\t\t\t\tconfidence: 0,\n\t\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\t\trequiresApproval: false,\n\t\t\t\t\t};\n\n\t\t\tthis.deps.saveLearningDecisionSnapshot(decision);\n\t\t\t// G3: learning-gate outcome. Codes/numbers only — never the proposal summary/memory text.\n\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.learningDecision,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\tconfidence: decision.confidence,\n\t\t\t\t\trequiresApproval: decision.requiresApproval,\n\t\t\t\t},\n\t\t\t});\n\t\t\t// G8: a proposal that needs human sign-off is an approval REQUEST. Codes/layer only —\n\t\t\t// never the proposal summary/memory text (those live only in the audit snapshot).\n\t\t\tif (decision.requiresApproval) {\n\t\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.approvalRequest,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tpayload: {\n\t\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// The gate's decision and the write's actual outcome are two different questions: the memory\n\t\t\t// tool can refuse a write (budget exceeded, drift, threat) via details.success:false without\n\t\t\t// throwing. Capture that outcome instead of assuming \"decision.kind === apply\" means it landed\n\t\t\t// — otherwise a refused write leaves a phantom \"apply\" audit whose rollback later fails\n\t\t\t// not-found (or, worse, misfires against whatever now occupies that text).\n\t\t\tconst applied = decision.kind === \"apply\" ? await this._applyReflectionWrite(write, signal) : false;\n\t\t\tconst writeFailed = decision.kind === \"apply\" && !applied;\n\t\t\tif (decision.kind !== \"no-op\") {\n\t\t\t\tauditSequence += 1;\n\t\t\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\t\t\tid: `audit-${auditSequence}`,\n\t\t\t\t\tproposalId,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\taction: writeFailed ? \"apply_failed\" : decision.kind === \"apply\" ? \"apply\" : \"propose\",\n\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\treasonCode: writeFailed ? APPLY_WRITE_REFUSED_REASON_CODE : decision.reasonCode,\n\t\t\t\t\tdecision,\n\t\t\t\t\t// No rollback plan on a failed apply — nothing durable landed, so there is nothing to undo.\n\t\t\t\t\trollback: writeFailed ? undefined : rollback,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).\n\t\t// Idempotent on reportId so a retried/duplicated pass cannot double-count.\n\t\tif (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {\n\t\t\tthis.deps.addSpawnedUsage(result.usage, { label: \"reflection\", reportId: input.reportId });\n\t\t}\n\t\treturn result;\n\t}\n\n\tgetLearningAuditRecords(): LearningAuditRecord[] {\n\t\treturn getLearningAuditSnapshots(this.deps.getSessionManager().getEntries());\n\t}\n\n\t/**\n\t * Roll back one applied durable learning change by executing the inverse operation recorded in\n\t * its audit record (memory ops run through the same bundled memory-tool path as the original\n\t * apply; promoted skills are archived). Appends a linked \"rollback\" audit record on success so\n\t * the change history stays complete and a change cannot be rolled back twice.\n\t */\n\tasync rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string }> {\n\t\tif (this.deps.isDisposed()) return { ok: false, reason: \"session_disposed\" };\n\n\t\tconst audits = this.getLearningAuditRecords();\n\t\tconst audit = audits.find((record) => record.id === auditId);\n\t\tif (!audit) return { ok: false, reason: \"audit_not_found\" };\n\t\tif (audit.action !== \"apply\") return { ok: false, reason: \"not_an_applied_change\" };\n\t\tif (audits.some((record) => record.action === \"rollback\" && record.rollbackOf === auditId)) {\n\t\t\treturn { ok: false, reason: \"already_rolled_back\" };\n\t\t}\n\t\tconst rollback = audit.rollback;\n\t\tif (!rollback) return { ok: false, reason: \"no_rollback_plan\" };\n\n\t\t// Every inverse must be VERIFIED-applied before the rollback audit is appended: a silently\n\t\t// failed inverse that still recorded \"rollback\" would permanently self-lock the change\n\t\t// behind already_rolled_back while the durable write is in fact still live.\n\t\tswitch (rollback.kind) {\n\t\t\tcase \"memory_remove\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!(await this._applyReflectionWrite({ kind: \"memory_remove\", target: rollback.target }))) {\n\t\t\t\t\treturn { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_restore\": {\n\t\t\t\tif (!rollback.target || rollback.previous === undefined) {\n\t\t\t\t\treturn { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\t}\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_replace\",\n\t\t\t\t\ttarget: rollback.target,\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_add\": {\n\t\t\t\tif (rollback.previous === undefined) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_add\",\n\t\t\t\t\tsection: \"MEMORY\",\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"archive_skill\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!this.deps.archivePromotedSkill(rollback.target)) {\n\t\t\t\t\treturn { ok: false, reason: \"skill_archive_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\tid: `${audit.id}-rollback`,\n\t\t\tproposalId: audit.proposalId,\n\t\t\tlayer: audit.layer,\n\t\t\taction: \"rollback\",\n\t\t\tsummary: `Rolled back: ${audit.summary}`,\n\t\t\treasonCode: \"user_requested_rollback\",\n\t\t\tdecision: audit.decision,\n\t\t\trollbackOf: audit.id,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t});\n\t\treturn { ok: true, reason: \"rollback_applied\" };\n\t}\n\n\t/**\n\t * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`\n\t * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring\n\t * isn't found there. Never throws (reflection must never break a turn); returns whether the write\n\t * actually applied so callers that MUST know — rollback's once-only accounting — can react instead\n\t * of recording a success that never happened.\n\t */\n\tprivate async _applyReflectionWrite(write: ReflectionWrite, signal?: AbortSignal): Promise<boolean> {\n\t\t// R7 memory-to-behavior: a recurring procedure is compiled into an executable skill file rather\n\t\t// than stored as a flat fact. Written under the agent skills dir so it loads like any user skill.\n\t\tif (write.kind === \"promote_skill\") {\n\t\t\treturn this._promoteReflectionSkill(write.name, write.description, write.body);\n\t\t}\n\n\t\ttype MemResult = { details?: { success?: boolean; error?: string } };\n\t\ttype MemExec = (\n\t\t\ttoolCallId: string,\n\t\t\tparams: { action: string; target: string; content?: string; oldContent?: string },\n\t\t\tsignal: AbortSignal | undefined,\n\t\t\tonUpdate: undefined,\n\t\t\tctx: undefined,\n\t\t) => Promise<MemResult>;\n\t\tconst memTool = this.deps\n\t\t\t.getMemoryManager()\n\t\t\t.getToolDefinitions()\n\t\t\t.find((t) => t.name === \"memory\");\n\t\tconst exec = memTool?.execute as unknown as MemExec | undefined;\n\t\tif (!exec) return false;\n\n\t\tconst run = (params: Parameters<MemExec>[1]) => exec(\"reflection\", params, signal, undefined, undefined);\n\n\t\tif (write.kind === \"memory_add\") {\n\t\t\ttry {\n\t\t\t\tconst res = await run({\n\t\t\t\t\taction: \"add\",\n\t\t\t\t\ttarget: write.section === \"USER\" ? \"user\" : \"memory\",\n\t\t\t\t\tcontent: write.text,\n\t\t\t\t});\n\t\t\t\treturn res?.details?.success === true;\n\t\t\t} catch {\n\t\t\t\t// best-effort; reflection writes must never throw into the turn loop\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports\n\t\t// outcomes via `details.success` (it catches its own errors rather than throwing). Only a\n\t\t// genuine \"not found in the file\" justifies trying the other file; a real failure for a file\n\t\t// (budget exceeded / drift) must NOT fall through and mutate the wrong target.\n\t\tfor (const target of [\"memory\", \"user\"] as const) {\n\t\t\ttry {\n\t\t\t\tconst params =\n\t\t\t\t\twrite.kind === \"memory_replace\"\n\t\t\t\t\t\t? { action: \"replace\", target, oldContent: write.target, content: write.text }\n\t\t\t\t\t\t: { action: \"remove\", target, oldContent: write.target };\n\t\t\t\tconst res = await run(params);\n\t\t\t\tif (res?.details?.success === true) return true; // applied\n\t\t\t\tif (!/not found/i.test(String(res?.details?.error ?? \"\"))) return false; // real failure — don't misapply\n\t\t\t\t// substring simply absent from this file — try the next target\n\t\t\t} catch {\n\t\t\t\t// defensive: if the tool ever does throw, try the next target\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any\n\t * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.\n\t */\n\tprivate _promoteReflectionSkill(rawName: string, description: string, body: string): boolean {\n\t\tconst name = rawName\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\")\n\t\t\t.slice(0, 64);\n\t\tif (!name || !body.trim()) return false;\n\t\ttry {\n\t\t\tconst dir = join(this.deps.getAgentDir(), \"skills\", name);\n\t\t\tconst file = join(dir, \"SKILL.md\");\n\t\t\tif (existsSync(file)) return false; // do not overwrite an existing skill\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\tconst safeDescription = description.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t\t// `promoted: true` marks this as reflection-generated so the curator (#32) can lifecycle-manage\n\t\t\t// it (archive/consolidate) WITHOUT ever touching hand-authored user skills.\n\t\t\tconst content = `---\\nname: ${name}\\ndescription: ${safeDescription}\\npromoted: true\\n---\\n\\n<!-- Auto-generated by the reflection engine (R7 memory-to-behavior). Review and refine. -->\\n\\n${body.trim()}\\n`;\n\t\t\twriteFileSync(file, content, \"utf-8\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// promotion must never break a turn\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}
|
|
@@ -108,10 +108,27 @@ export class ReflectionController {
|
|
|
108
108
|
};
|
|
109
109
|
const messages = await runAgentLoop(opts.messages, childContext, loopConfig, () => { }, opts.signal, agent.streamFn);
|
|
110
110
|
const assistantMessages = messages.filter((message) => message.role === "assistant");
|
|
111
|
-
|
|
111
|
+
let finalAssistant = assistantMessages.at(-1);
|
|
112
112
|
if (!finalAssistant) {
|
|
113
113
|
throw new Error("runIsolatedCompletion: child loop produced no assistant message");
|
|
114
114
|
}
|
|
115
|
+
const hasFinalText = finalAssistant.content.some((content) => content.type === "text" && content.text.trim().length > 0);
|
|
116
|
+
const endedOnToolCall = finalAssistant.content.some((content) => content.type === "toolCall");
|
|
117
|
+
if (opts.finalTextPrompt && !hasFinalText && endedOnToolCall && !opts.signal?.aborted) {
|
|
118
|
+
// A hard turn bound may stop immediately after successful tool execution. Preserve that bound,
|
|
119
|
+
// then allow exactly one tool-free synthesis call so the gathered work is not thrown away.
|
|
120
|
+
const finalizationMessages = await agent.convertToLlm(messages);
|
|
121
|
+
const finalizationStream = await agent.streamFn(model, {
|
|
122
|
+
systemPrompt: opts.systemPrompt,
|
|
123
|
+
messages: [
|
|
124
|
+
...finalizationMessages,
|
|
125
|
+
{ role: "user", content: opts.finalTextPrompt, timestamp: Date.now() },
|
|
126
|
+
],
|
|
127
|
+
tools: [],
|
|
128
|
+
}, options);
|
|
129
|
+
finalAssistant = await finalizationStream.result();
|
|
130
|
+
assistantMessages.push(finalAssistant);
|
|
131
|
+
}
|
|
115
132
|
const usage = assistantMessages.reduce((total, message) => ({
|
|
116
133
|
input: total.input + message.usage.input,
|
|
117
134
|
output: total.output + message.usage.output,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reflection-controller.js","sourceRoot":"","sources":["../../src/core/reflection-controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAIN,YAAY,GAEZ,MAAM,2BAA2B,CAAC;AAKnC,OAAO,EAAE,8BAA8B,EAA+B,MAAM,gCAAgC,CAAC;AAC7G,OAAO,EACN,+BAA+B,EAC/B,2BAA2B,EAC3B,gCAAgC,EAChC,yBAAyB,EAEzB,2BAA2B,EAC3B,8BAA8B,GAC9B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAEN,YAAY,EACZ,gBAAgB,GAGhB,MAAM,iCAAiC,CAAC;AA6CzC,MAAM,OAAO,oBAAoB;IACf,IAAI,CAA2B;IAEhD,YAAY,IAA8B,EAAE;QAC3C,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IAAA,CACjB;IAED;;;;;;;;;;;OAWG;IACH,KAAK,CAAC,qBAAqB,CAAC,IAA+B,EAAqC;QAC/F,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACjD,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,KAAK,CAAC;QAElD,sGAAsG;QACtG,MAAM,OAAO,GAAY;YACxB,YAAY,EAAE,IAAI,CAAC,YAAY;YAC/B,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;SACvB,CAAC;QAEF,+FAA+F;QAC/F,gDAAgD;QAChD,MAAM,OAAO,GAAwB;YACpC,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,MAAM;YAC7C,SAAS,EAAE,aAAa;SACxB,CAAC;QAEF,2FAA2F;QAC3F,0FAAwF;QACxF,yEAAyE;QACzE,IAAI,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,CAAC;YACnC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YAC3E,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;gBACd,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC7B,CAAC;YACD,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;YAC7B,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC;QAChC,CAAC;QAED,IAAI,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC;YAC1C,MAAM,mBAAmB,GAAG,eAAe,EAAE,QAAQ,KAAK,KAAK,CAAC,QAAQ,IAAI,eAAe,CAAC,EAAE,KAAK,KAAK,CAAC,EAAE,CAAC;YAC5G,MAAM,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,2BAA2B,CAAC,KAAK,CAAC,CAAC;YAC1E,MAAM,iBAAiB,GACtB,OAAO,IAAI,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACrG,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,iBAAiB,CAAC,CAAC,CAAC;YAC9D,IAAI,cAAc,GAAG,CAAC,CAAC;YACvB,MAAM,YAAY,GAAiB;gBAClC,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,QAAQ,EAAE,EAAE;gBACZ,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;aACtB,CAAC;YACF,MAAM,UAAU,GAAoB;gBACnC,KAAK;gBACL,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,MAAM;gBAC7C,SAAS,EAAE,aAAa;gBACxB,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtE,WAAW,EAAE,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;gBACjD,oBAAoB;gBACpB,uBAAuB,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC,SAAS;gBACxF,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;gBACtC,eAAe,EAAE,KAAK,CAAC,eAAe;gBACtC,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,IAAI,QAAQ,CAAC,CAAC;gBAC/E,aAAa,EAAE,YAAY;gBAC3B,wBAAwB,EAAE,KAAK,CAAC,wBAAwB;gBACxD,wBAAwB,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC,SAAS;gBAC1F,iCAAiC,EAAE,KAAK,CAAC,iCAAiC;gBAC1E,0BAA0B,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC,CAAC,SAAS;gBAC9F,cAAc,EAAE,IAAI,CAAC,cAAc;gBACnC,aAAa,EAAE,IAAI,CAAC,aAAa;gBACjC,mBAAmB,EAAE,GAAG,EAAE,CAAC;oBAC1B,cAAc,IAAI,CAAC,CAAC;oBACpB,OAAO,cAAc,IAAI,QAAQ,CAAC;gBAAA,CAClC;gBACD,YAAY,EAAE,KAAK,CAAC,YAAY;aAChC,CAAC;YACF,MAAM,QAAQ,GAAG,MAAM,YAAY,CAClC,IAAI,CAAC,QAAQ,EACb,YAAY,EACZ,UAAU,EACV,GAAG,EAAE,CAAC,EAAC,CAAC,EACR,IAAI,CAAC,MAAM,EACX,KAAK,CAAC,QAAQ,CACd,CAAC;YACF,MAAM,iBAAiB,GAAG,QAAQ,CAAC,MAAM,CACxC,CAAC,OAAO,EAA+B,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,CACtE,CAAC;YACF,MAAM,cAAc,GAAG,iBAAiB,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAChD,IAAI,CAAC,cAAc,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,iEAAiE,CAAC,CAAC;YACpF,CAAC;YACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CACrC,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;gBACpB,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK;gBACxC,MAAM,EAAE,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM;gBAC3C,SAAS,EAAE,KAAK,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS;gBACpD,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU;gBACvD,WAAW,EAAE,KAAK,CAAC,WAAW,GAAG,OAAO,CAAC,KAAK,CAAC,WAAW;gBAC1D,IAAI,EAAE;oBACL,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK;oBAClD,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM;oBACrD,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS;oBAC9D,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU;oBACjE,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK;iBAClD;aACD,CAAC,EACF;gBACC,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,SAAS,EAAE,CAAC;gBACZ,UAAU,EAAE,CAAC;gBACb,WAAW,EAAE,CAAC;gBACd,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;aACpE,CACD,CAAC;YACF,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO;iBACjC,MAAM,CAAC,CAAC,OAAO,EAA0B,EAAE,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;iBACpE,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;iBAC9B,IAAI,CAAC,EAAE,CAAC,CAAC;YACX,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,CAAC,UAAU,EAAE,CAAC;QAC/D,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;QAC5E,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO;aACzB,MAAM,CAAC,CAAC,CAAC,EAAoB,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;aAClD,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,EAAE,CAAC,CAAC;QACX,MAAM,KAAK,GAAU,MAAM,CAAC,KAAK,IAAI;YACpC,KAAK,EAAE,CAAC;YACR,MAAM,EAAE,CAAC;YACT,SAAS,EAAE,CAAC;YACZ,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,CAAC;YACd,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE;SACpE,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;IAAA,CACtD;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,iBAAiB,CAAC,KAQvB,EAAoC;QACpC,IAAI,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,IAAI,CAAC;QACtE,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACzC,IAAI,IAAI,CAAC,GAAG,KAAK,MAAM;YAAE,OAAO,IAAI,CAAC;QAErC,8FAA8F;QAC9F,qEAAqE;QACrE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM;YAC1B,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;YAClE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAEnC,MAAM,QAAQ,GAAG,CAAC,YAAoB,EAAE,UAAkB,EAAE,EAAE,CAC7D,IAAI,CAAC,qBAAqB,CAAC;YAC1B,YAAY;YACZ,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAClG,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,aAAa,EAAE,KAAK,CAAC,aAAa,IAAI,KAAK;YAC3C,SAAS,EAAE,IAAI,CAAC,WAAW;YAC3B,MAAM;YACN,wFAAsF;YACtF,kDAAkD;YAClD,cAAc,EAAE,OAAO;SACvB,CAAC,CAAC;QAEJ,MAAM,MAAM,GAAG,MAAM,IAAI,gBAAgB,EAAE,CAAC,OAAO,CAAC;YACnD,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,+FAA+F;YAC/F,yCAAyC;YACzC,cAAc,EAAE,IAAI,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,2BAA2B,EAAE,IAAI,EAAE;YAChF,IAAI;YACJ,QAAQ;SACR,CAAC,CAAC;QAEH,+FAA+F;QAC/F,sCAAsC;QACtC,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,MAAM,CAAC;QAE1C,wFAAwF;QACxF,0FAA0F;QAC1F,iGAA+F;QAC/F,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,kBAAkB,EAAE,CAAC,yBAAyB,EAAE,CAAC;QAC1E,0FAA0F;QAC1F,yFAAyF;QACzF,2FAAyF;QACzF,IAAI,aAAa,GAAG,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,MAAM,CAAC;QACjG,+FAA+F;QAC/F,+FAA+F;QAC/F,+EAA+E;QAC/E,MAAM,gBAAgB,GAAG,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC/E,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YACnC,UAAU,IAAI,CAAC,CAAC;YAChB,MAAM,UAAU,GAAG,GAAG,KAAK,CAAC,QAAQ,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;YACtE,MAAM,QAAQ,GAAG,2BAA2B,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;YAChE,MAAM,QAAQ,GAAG,8BAA8B,CAAC,KAAK,CAAC,CAAC;YACvD,IAAI,YAAY,GAAG,CAAC,CAAC;YACrB,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,IAAI,CAAC;oBACJ,YAAY,GAAG,gBAAgB,CAAC,SAAS,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;gBAC7F,CAAC;gBAAC,MAAM,CAAC;oBACR,oFAAoF;oBACpF,gFAAgF;oBAChF,YAAY,GAAG,CAAC,CAAC;gBAClB,CAAC;YACF,CAAC;YACD,MAAM,QAAQ,GAAqB,MAAM,CAAC,OAAO;gBAChD,CAAC,CAAC,wBAAwB,CAAC;oBACzB,QAAQ;oBACR,UAAU,EAAE,MAAM,CAAC,0BAA0B;oBAC7C,YAAY;oBACZ,mFAAiF;oBACjF,qFAAmF;oBACnF,yEAAyE;oBACzE,cAAc,EAAE,gCAAgC,CAAC,KAAK,CAAC;oBACvD,QAAQ,EAAE;wBACT,OAAO,EAAE,IAAI;wBACb,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;wBACzC,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;wBAC/C,eAAe,EAAE,MAAM,CAAC,eAAe;wBACvC,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;wBACrD,mBAAmB,EAAE,MAAM,CAAC,mBAAmB;wBAC/C,sBAAsB,EAAE,MAAM,CAAC,sBAAsB;qBACrD;iBACD,CAAC;gBACH,CAAC,CAAC;oBACA,IAAI,EAAE,OAAO;oBACb,UAAU,EAAE,uCAAuC;oBACnD,UAAU,EAAE,CAAC;oBACb,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,gBAAgB,EAAE,KAAK;iBACvB,CAAC;YAEJ,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,QAAQ,CAAC,CAAC;YACjD,4FAA0F;YAC1F,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC;gBAC/B,IAAI,EAAE,8BAA8B,CAAC,gBAAgB;gBACrD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;gBACnC,OAAO,EAAE;oBACR,IAAI,EAAE,QAAQ,CAAC,IAAI;oBACnB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC/B,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;iBAC3C;aACD,CAAC,CAAC;YACH,wFAAsF;YACtF,kFAAkF;YAClF,IAAI,QAAQ,CAAC,gBAAgB,EAAE,CAAC;gBAC/B,IAAI,CAAC,IAAI,CAAC,qBAAqB,CAAC;oBAC/B,IAAI,EAAE,8BAA8B,CAAC,eAAe;oBACpD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;oBACnC,OAAO,EAAE;wBACR,IAAI,EAAE,QAAQ,CAAC,IAAI;wBACnB,UAAU,EAAE,QAAQ,CAAC,UAAU;wBAC/B,KAAK,EAAE,QAAQ,CAAC,KAAK;qBACrB;iBACD,CAAC,CAAC;YACJ,CAAC;YACD,6FAA6F;YAC7F,6FAA6F;YAC7F,+FAA+F;YAC/F,0FAAwF;YACxF,2EAA2E;YAC3E,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,qBAAqB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YACpG,MAAM,WAAW,GAAG,QAAQ,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,OAAO,CAAC;YAC1D,IAAI,QAAQ,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC/B,aAAa,IAAI,CAAC,CAAC;gBACnB,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE;oBAC1D,EAAE,EAAE,SAAS,aAAa,EAAE;oBAC5B,UAAU;oBACV,KAAK,EAAE,QAAQ,CAAC,KAAK;oBACrB,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;oBACtF,OAAO,EAAE,QAAQ,CAAC,OAAO;oBACzB,UAAU,EAAE,WAAW,CAAC,CAAC,CAAC,+BAA+B,CAAC,CAAC,CAAC,QAAQ,CAAC,UAAU;oBAC/E,QAAQ;oBACR,8FAA4F;oBAC5F,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ;oBAC5C,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;iBACnC,CAAC,CAAC;YACJ,CAAC;QACF,CAAC;QAED,8FAA8F;QAC9F,2EAA2E;QAC3E,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,EAAE,CAAC;YACjE,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5F,CAAC;QACD,OAAO,MAAM,CAAC;IAAA,CACd;IAED,uBAAuB,GAA0B;QAChD,OAAO,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC;IAAA,CAC7E;IAED;;;;;OAKG;IACH,KAAK,CAAC,qBAAqB,CAAC,OAAe,EAA4C;QACtF,IAAI,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAE7E,MAAM,MAAM,GAAG,IAAI,CAAC,uBAAuB,EAAE,CAAC;QAC9C,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,OAAO,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;QAC5D,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QACpF,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,UAAU,KAAK,OAAO,CAAC,EAAE,CAAC;YAC5F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QACrD,CAAC;QACD,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;QAChC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QAEhE,2FAA2F;QAC3F,uFAAuF;QACvF,4EAA4E;QAC5E,QAAQ,QAAQ,CAAC,IAAI,EAAE,CAAC;YACvB,KAAK,eAAe,EAAE,CAAC;gBACtB,IAAI,CAAC,QAAQ,CAAC,MAAM;oBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;gBAC9E,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,qBAAqB,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC;oBAC7F,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;gBACvD,CAAC;gBACD,MAAM;YACP,CAAC;YACD,KAAK,gBAAgB,EAAE,CAAC;gBACvB,IAAI,CAAC,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;oBACzD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;gBACzD,CAAC;gBACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC;oBAChD,IAAI,EAAE,gBAAgB;oBACtB,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,IAAI,EAAE,QAAQ,CAAC,QAAQ;iBACvB,CAAC,CAAC;gBACH,IAAI,CAAC,OAAO;oBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;gBACpE,MAAM;YACP,CAAC;YACD,KAAK,YAAY,EAAE,CAAC;gBACnB,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS;oBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;gBAC7F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC;oBAChD,IAAI,EAAE,YAAY;oBAClB,OAAO,EAAE,QAAQ;oBACjB,IAAI,EAAE,QAAQ,CAAC,QAAQ;iBACvB,CAAC,CAAC;gBACH,IAAI,CAAC,OAAO;oBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;gBACpE,MAAM;YACP,CAAC;YACD,KAAK,eAAe,EAAE,CAAC;gBACtB,IAAI,CAAC,QAAQ,CAAC,MAAM;oBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,EAAE,CAAC;gBAC9E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;oBACtD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,CAAC;gBACtD,CAAC;gBACD,MAAM;YACP,CAAC;QACF,CAAC;QAED,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE;YAC1D,EAAE,EAAE,GAAG,KAAK,CAAC,EAAE,WAAW;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;YAC5B,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,gBAAgB,KAAK,CAAC,OAAO,EAAE;YACxC,UAAU,EAAE,yBAAyB;YACrC,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,UAAU,EAAE,KAAK,CAAC,EAAE;YACpB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAAA,CAChD;IAED;;;;;;OAMG;IACK,KAAK,CAAC,qBAAqB,CAAC,KAAsB,EAAE,MAAoB,EAAoB;QACnG,gGAAgG;QAChG,kGAAkG;QAClG,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACpC,OAAO,IAAI,CAAC,uBAAuB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAChF,CAAC;QAUD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI;aACvB,gBAAgB,EAAE;aAClB,kBAAkB,EAAE;aACpB,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;QACnC,MAAM,IAAI,GAAG,OAAO,EAAE,OAAyC,CAAC;QAChE,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QAExB,MAAM,GAAG,GAAG,CAAC,MAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;QAEzG,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACjC,IAAI,CAAC;gBACJ,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC;oBACrB,MAAM,EAAE,KAAK;oBACb,MAAM,EAAE,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ;oBACpD,OAAO,EAAE,KAAK,CAAC,IAAI;iBACnB,CAAC,CAAC;gBACH,OAAO,GAAG,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;YACvC,CAAC;YAAC,MAAM,CAAC;gBACR,qEAAqE;gBACrE,OAAO,KAAK,CAAC;YACd,CAAC;QACF,CAAC;QAED,iGAA+F;QAC/F,0FAA0F;QAC1F,6FAA6F;QAC7F,+EAA+E;QAC/E,KAAK,MAAM,MAAM,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAU,EAAE,CAAC;YAClD,IAAI,CAAC;gBACJ,MAAM,MAAM,GACX,KAAK,CAAC,IAAI,KAAK,gBAAgB;oBAC9B,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE;oBAC9E,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;gBAC3D,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,MAAM,CAAC,CAAC;gBAC9B,IAAI,GAAG,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI;oBAAE,OAAO,IAAI,CAAC,CAAC,UAAU;gBAC3D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;oBAAE,OAAO,KAAK,CAAC,CAAC,kCAAgC;gBACzG,iEAA+D;YAChE,CAAC;YAAC,MAAM,CAAC;gBACR,8DAA8D;YAC/D,CAAC;QACF,CAAC;QACD,OAAO,KAAK,CAAC;IAAA,CACb;IAED;;;OAGG;IACK,uBAAuB,CAAC,OAAe,EAAE,WAAmB,EAAE,IAAY,EAAW;QAC5F,MAAM,IAAI,GAAG,OAAO;aAClB,IAAI,EAAE;aACN,WAAW,EAAE;aACb,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC;aAC5B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;aACvB,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACnC,IAAI,UAAU,CAAC,IAAI,CAAC;gBAAE,OAAO,KAAK,CAAC,CAAC,qCAAqC;YACzE,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,MAAM,eAAe,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YACpE,gGAAgG;YAChG,4EAA4E;YAC5E,MAAM,OAAO,GAAG,cAAc,IAAI,kBAAkB,eAAe,4HAA4H,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC;YAC/M,aAAa,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC;QACb,CAAC;QAAC,MAAM,CAAC;YACR,oCAAoC;YACpC,OAAO,KAAK,CAAC;QACd,CAAC;IAAA,CACD;CACD","sourcesContent":["/**\n * Native reflection + learning-write controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection\n * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that\n * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session\n * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or\n * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live\n * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the\n * whole AgentSession.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype Agent,\n\ttype AgentContext,\n\ttype AgentLoopConfig,\n\trunAgentLoop,\n\ttype ThinkingLevel,\n} from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Api, AssistantMessage, Context, Model, SimpleStreamOptions, TextContent, Usage } from \"@caupulican/pi-ai\";\nimport type { IsolatedCompletionOptions, IsolatedCompletionResult } from \"./agent-session.ts\";\nimport type { LearningDecision } from \"./autonomy/contracts.ts\";\nimport { AUTONOMY_TELEMETRY_EVENT_TYPES, type AutonomyTelemetryEvent } from \"./autonomy/telemetry-events.ts\";\nimport {\n\tAPPLY_WRITE_REFUSED_REASON_CODE,\n\tappendLearningAuditSnapshot,\n\tcontradictionsForReflectionWrite,\n\tgetLearningAuditSnapshots,\n\ttype LearningAuditRecord,\n\tproposalFromReflectionWrite,\n\trollbackPlanForReflectionWrite,\n} from \"./learning/learning-audit.ts\";\nimport { evaluateLearningDecision } from \"./learning/learning-gate.ts\";\nimport { ObservationStore, observationKey } from \"./learning/observation-store.ts\";\nimport {\n\ttype DemandSignals,\n\tdecideDemand,\n\tReflectionEngine,\n\ttype ReflectionResult,\n\ttype ReflectionWrite,\n} from \"./learning/reflection-engine.ts\";\nimport type { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface ReflectionControllerDeps {\n\t/** Current session model (fallback for an isolated call that omits its own model). */\n\tgetModel(): Model<any> | undefined;\n\t/** The underlying agent — its `streamFn` runs the isolated completion. */\n\tgetAgent(): Agent;\n\t/** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */\n\tisRawStreamSimple(): boolean;\n\t/** Model registry for API-key/header resolution on the raw-provider path. */\n\tgetModelRegistry(): ModelRegistry;\n\t/** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */\n\tgetMemoryManager(): MemoryManager;\n\t/** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Session log — audit snapshots and learning-audit reads go through this. */\n\tgetSessionManager(): SessionManager;\n\t/** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */\n\tgetAgentDir(): string;\n\t/** Child sessions must not learn — the pass returns null for them. */\n\tisChildSession(): boolean;\n\t/** Disposal short-circuits: no completion, no writes against a dead session. */\n\tisDisposed(): boolean;\n\t/** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */\n\tgetReflectionSignal(): AbortSignal;\n\t/** Archive a promoted skill (rollback of a `promote_skill` write). */\n\tarchivePromotedSkill(name: string): boolean;\n\t/** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */\n\temitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;\n\t/** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined;\n\t/** Persist a learning-gate decision snapshot to the session log. */\n\tsaveLearningDecisionSnapshot(decision: LearningDecision): string;\n\t/** Resolve text-tool fallback for the selected isolated model, not the foreground model. */\n\tresolveTextToolCallProtocol(model: Model<Api>): SimpleStreamOptions[\"textToolCallProtocol\"];\n\t/** Ensure a managed-local model is running/resident before any isolated lane calls it. */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class ReflectionController {\n\tprivate readonly deps: ReflectionControllerDeps;\n\n\tconstructor(deps: ReflectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Run an LLM completion fully ISOLATED from the main session — the load-bearing primitive for\n\t * reflection and bounded child lanes (adaptive-agent design §6c/§7).\n\t *\n\t * Isolation invariants (audited by codex): builds fresh context (no main history), defaults to no\n\t * tools, and passes **no `sessionId`**. A tool-enabled call receives only caller-owned tools and\n\t * hooks, and is turn-bounded. It cannot mutate `agent.state.messages`, append session entries, or\n\t * touch the foreground tool registry. Mirrors `generateSummary()`'s one-shot mechanics otherwise.\n\t *\n\t * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection\n\t * microtask — decide whether to act); it does not throw on a model-level error.\n\t */\n\tasync runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult> {\n\t\tconst model = opts.model ?? this.deps.getModel();\n\t\tif (!model) {\n\t\t\tthrow new Error(\"runIsolatedCompletion: no model available\");\n\t\t}\n\t\tawait this.deps.ensureModelReady(model);\n\t\tconst thinkingLevel = opts.thinkingLevel ?? \"off\";\n\n\t\t// Fresh, isolated context: explicit messages, caller-owned tools only, nothing from the main session.\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\tmessages: opts.messages,\n\t\t\ttools: opts.tools ?? [],\n\t\t};\n\n\t\t// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing\n\t\t// can entangle this call with the main session.\n\t\tconst options: SimpleStreamOptions = {\n\t\t\tmaxTokens: opts.maxTokens,\n\t\t\tsignal: opts.signal,\n\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\treasoning: thinkingLevel,\n\t\t};\n\n\t\t// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.\n\t\t// Throw only when auth genuinely fails — providers that authenticate without an API key\n\t\t// (OAuth, local no-key) legitimately return ok with an undefined apiKey.\n\t\tif (this.deps.isRawStreamSimple()) {\n\t\t\tconst auth = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\toptions.apiKey = auth.apiKey;\n\t\t\toptions.headers = auth.headers;\n\t\t}\n\n\t\tif (opts.tools && opts.tools.length > 0) {\n\t\t\tconst agent = this.deps.getAgent();\n\t\t\tconst foregroundModel = agent.state.model;\n\t\t\tconst usesForegroundModel = foregroundModel?.provider === model.provider && foregroundModel.id === model.id;\n\t\t\tconst textToolCallProtocol = this.deps.resolveTextToolCallProtocol(model);\n\t\t\tconst requestedMaxTurns =\n\t\t\t\ttypeof opts.maxTurns === \"number\" && Number.isFinite(opts.maxTurns) ? Math.floor(opts.maxTurns) : 6;\n\t\t\tconst maxTurns = Math.max(1, Math.min(12, requestedMaxTurns));\n\t\t\tlet completedTurns = 0;\n\t\t\tconst childContext: AgentContext = {\n\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\tmessages: [],\n\t\t\t\ttools: [...opts.tools],\n\t\t\t};\n\t\t\tconst loopConfig: AgentLoopConfig = {\n\t\t\t\tmodel,\n\t\t\t\tmaxTokens: opts.maxTokens,\n\t\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\t\treasoning: thinkingLevel,\n\t\t\t\t...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n\t\t\t\t...(options.headers !== undefined ? { headers: options.headers } : {}),\n\t\t\t\ttemperature: textToolCallProtocol ? 0 : undefined,\n\t\t\t\ttextToolCallProtocol,\n\t\t\t\tonTextToolProtocolParse: usesForegroundModel ? agent.onTextToolProtocolParse : undefined,\n\t\t\t\ttransport: agent.transport,\n\t\t\t\tthinkingBudgets: agent.thinkingBudgets,\n\t\t\t\tmaxRetryDelayMs: agent.maxRetryDelayMs,\n\t\t\t\tmaxStallTurns: Math.max(2, Math.min(maxTurns, agent.maxStallTurns ?? maxTurns)),\n\t\t\t\ttoolExecution: \"sequential\",\n\t\t\t\ttoolArgumentTeachEnabled: agent.toolArgumentTeachEnabled,\n\t\t\t\tonToolArgumentValidation: usesForegroundModel ? agent.onToolArgumentValidation : undefined,\n\t\t\t\ttoolValidationEscalationThreshold: agent.toolValidationEscalationThreshold,\n\t\t\t\tonToolValidationEscalation: usesForegroundModel ? agent.onToolValidationEscalation : undefined,\n\t\t\t\tbeforeToolCall: opts.beforeToolCall,\n\t\t\t\tafterToolCall: opts.afterToolCall,\n\t\t\t\tshouldStopAfterTurn: () => {\n\t\t\t\t\tcompletedTurns += 1;\n\t\t\t\t\treturn completedTurns >= maxTurns;\n\t\t\t\t},\n\t\t\t\tconvertToLlm: agent.convertToLlm,\n\t\t\t};\n\t\t\tconst messages = await runAgentLoop(\n\t\t\t\topts.messages,\n\t\t\t\tchildContext,\n\t\t\t\tloopConfig,\n\t\t\t\t() => {},\n\t\t\t\topts.signal,\n\t\t\t\tagent.streamFn,\n\t\t\t);\n\t\t\tconst assistantMessages = messages.filter(\n\t\t\t\t(message): message is AssistantMessage => message.role === \"assistant\",\n\t\t\t);\n\t\t\tconst finalAssistant = assistantMessages.at(-1);\n\t\t\tif (!finalAssistant) {\n\t\t\t\tthrow new Error(\"runIsolatedCompletion: child loop produced no assistant message\");\n\t\t\t}\n\t\t\tconst usage = assistantMessages.reduce<Usage>(\n\t\t\t\t(total, message) => ({\n\t\t\t\t\tinput: total.input + message.usage.input,\n\t\t\t\t\toutput: total.output + message.usage.output,\n\t\t\t\t\tcacheRead: total.cacheRead + message.usage.cacheRead,\n\t\t\t\t\tcacheWrite: total.cacheWrite + message.usage.cacheWrite,\n\t\t\t\t\ttotalTokens: total.totalTokens + message.usage.totalTokens,\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: total.cost.input + message.usage.cost.input,\n\t\t\t\t\t\toutput: total.cost.output + message.usage.cost.output,\n\t\t\t\t\t\tcacheRead: total.cost.cacheRead + message.usage.cost.cacheRead,\n\t\t\t\t\t\tcacheWrite: total.cost.cacheWrite + message.usage.cost.cacheWrite,\n\t\t\t\t\t\ttotal: total.cost.total + message.usage.cost.total,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst text = finalAssistant.content\n\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t.map((content) => content.text)\n\t\t\t\t.join(\"\");\n\t\t\treturn { text, usage, stopReason: finalAssistant.stopReason };\n\t\t}\n\n\t\tconst stream = await this.deps.getAgent().streamFn(model, context, options);\n\t\tconst result = await stream.result();\n\t\tconst text = result.content\n\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\");\n\t\tconst usage: Usage = result.usage ?? {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t};\n\t\treturn { text, usage, stopReason: result.stopReason };\n\t}\n\n\t/**\n\t * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the\n\t * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the\n\t * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token\n\t * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.\n\t *\n\t * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass\n\t * is best-effort: a model/parse error yields no writes, never throws into the caller.\n\t */\n\tasync runReflectionPass(input: {\n\t\tsignals: DemandSignals;\n\t\trecentTurnText: string;\n\t\tmodel?: Model<any>;\n\t\tthinkingLevel?: ThinkingLevel;\n\t\tsignal?: AbortSignal;\n\t\t/** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */\n\t\treportId?: string;\n\t}): Promise<ReflectionResult | null> {\n\t\tif (this.deps.isChildSession() || this.deps.isDisposed()) return null;\n\t\tconst plan = decideDemand(input.signals);\n\t\tif (plan.act === \"skip\") return null;\n\n\t\t// Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the\n\t\t// in-flight completion (input.signal can add a more specific abort).\n\t\tconst signal = input.signal\n\t\t\t? AbortSignal.any([input.signal, this.deps.getReflectionSignal()])\n\t\t\t: this.deps.getReflectionSignal();\n\n\t\tconst complete = (systemPrompt: string, userPrompt: string) =>\n\t\t\tthis.runIsolatedCompletion({\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: [{ role: \"user\", content: [{ type: \"text\", text: userPrompt }], timestamp: Date.now() }],\n\t\t\t\tmodel: input.model,\n\t\t\t\tthinkingLevel: input.thinkingLevel ?? \"low\",\n\t\t\t\tmaxTokens: plan.tokenBudget,\n\t\t\t\tsignal,\n\t\t\t\t// The reflection system prompt is static (#33) — let the provider cache the prefix so\n\t\t\t\t// repeated passes only pay for the variable tail.\n\t\t\t\tcacheRetention: \"short\",\n\t\t\t});\n\n\t\tconst result = await new ReflectionEngine().reflect({\n\t\t\trecentTurnText: input.recentTurnText,\n\t\t\t// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write\n\t\t\t// sees writes made earlier this session.\n\t\t\texistingMemory: this.deps.getMemoryManager().buildSystemPromptBlockFresh() || \"\",\n\t\t\tplan,\n\t\t\tcomplete,\n\t\t});\n\n\t\t// Bug #21: if the session was disposed while the completion was in flight, do NOT write memory\n\t\t// or skills against the dead session.\n\t\tif (this.deps.isDisposed()) return result;\n\n\t\t// Learning apply policy: every durable write is converted to a proposal, decided by the\n\t\t// learning gate, and audited with a rollback plan. With the policy disabled (default) the\n\t\t// legacy direct-apply behavior is preserved — but now leaves audit records with rollback info.\n\t\tconst policy = this.deps.getSettingsManager().getLearningPolicySettings();\n\t\t// The audit id sequence counts STORED snapshots only: it reseeds from the stored count on\n\t\t// every pass, so advancing it for a no-op (which stores nothing) would make later passes\n\t\t// reuse ids — and rollback keys on the id, so a collision blocks or misdirects rollback.\n\t\tlet auditSequence = getLearningAuditSnapshots(this.deps.getSessionManager().getEntries()).length;\n\t\t// G6 evidence strength: durable proposals accumulate observation counts across passes/sessions\n\t\t// so the gate can distinguish a one-off cue from a repeatedly-confirmed lesson. Built once per\n\t\t// pass; every increment is best-effort (store IO must never break reflection).\n\t\tconst observationStore = ObservationStore.forAgentDir(this.deps.getAgentDir());\n\t\tlet writeIndex = 0;\n\t\tfor (const write of result.writes) {\n\t\t\twriteIndex += 1;\n\t\t\tconst proposalId = `${input.reportId ?? \"reflection\"}-w${writeIndex}`;\n\t\t\tconst proposal = proposalFromReflectionWrite(write, proposalId);\n\t\t\tconst rollback = rollbackPlanForReflectionWrite(write);\n\t\t\tlet observations = 1;\n\t\t\tif (policy.enabled) {\n\t\t\t\ttry {\n\t\t\t\t\tobservations = observationStore.increment(observationKey(proposal.layer, proposal.summary));\n\t\t\t\t} catch {\n\t\t\t\t\t// A store read/write failure falls back to a fresh count of 1, which keeps the gate\n\t\t\t\t\t// proposal-first (never spuriously auto-applies) rather than crashing the pass.\n\t\t\t\t\tobservations = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst decision: LearningDecision = policy.enabled\n\t\t\t\t? evaluateLearningDecision({\n\t\t\t\t\t\tproposal,\n\t\t\t\t\t\tconfidence: policy.reflectionSourceConfidence,\n\t\t\t\t\t\tobservations,\n\t\t\t\t\t\t// A replace/remove supersedes an existing durable fact — the reflection engine's\n\t\t\t\t\t\t// confront-before-write conflict signal — so it routes through approval instead of\n\t\t\t\t\t\t// silently overwriting prior memory. Additive writes contradict nothing.\n\t\t\t\t\t\tcontradictions: contradictionsForReflectionWrite(write),\n\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\tautoApplyEnabled: policy.autoApplyEnabled,\n\t\t\t\t\t\t\tconfidenceThreshold: policy.confidenceThreshold,\n\t\t\t\t\t\t\tminObservations: policy.minObservations,\n\t\t\t\t\t\t\tallowedAutoApplyLayers: policy.allowedAutoApplyLayers,\n\t\t\t\t\t\t\trequireRollbackPlan: policy.requireRollbackPlan,\n\t\t\t\t\t\t\tautoApplySupersessions: policy.autoApplySupersessions,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tkind: \"apply\",\n\t\t\t\t\t\treasonCode: \"learning_policy_disabled_legacy_apply\",\n\t\t\t\t\t\tconfidence: 0,\n\t\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\t\trequiresApproval: false,\n\t\t\t\t\t};\n\n\t\t\tthis.deps.saveLearningDecisionSnapshot(decision);\n\t\t\t// G3: learning-gate outcome. Codes/numbers only — never the proposal summary/memory text.\n\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.learningDecision,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\tconfidence: decision.confidence,\n\t\t\t\t\trequiresApproval: decision.requiresApproval,\n\t\t\t\t},\n\t\t\t});\n\t\t\t// G8: a proposal that needs human sign-off is an approval REQUEST. Codes/layer only —\n\t\t\t// never the proposal summary/memory text (those live only in the audit snapshot).\n\t\t\tif (decision.requiresApproval) {\n\t\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.approvalRequest,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tpayload: {\n\t\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// The gate's decision and the write's actual outcome are two different questions: the memory\n\t\t\t// tool can refuse a write (budget exceeded, drift, threat) via details.success:false without\n\t\t\t// throwing. Capture that outcome instead of assuming \"decision.kind === apply\" means it landed\n\t\t\t// — otherwise a refused write leaves a phantom \"apply\" audit whose rollback later fails\n\t\t\t// not-found (or, worse, misfires against whatever now occupies that text).\n\t\t\tconst applied = decision.kind === \"apply\" ? await this._applyReflectionWrite(write, signal) : false;\n\t\t\tconst writeFailed = decision.kind === \"apply\" && !applied;\n\t\t\tif (decision.kind !== \"no-op\") {\n\t\t\t\tauditSequence += 1;\n\t\t\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\t\t\tid: `audit-${auditSequence}`,\n\t\t\t\t\tproposalId,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\taction: writeFailed ? \"apply_failed\" : decision.kind === \"apply\" ? \"apply\" : \"propose\",\n\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\treasonCode: writeFailed ? APPLY_WRITE_REFUSED_REASON_CODE : decision.reasonCode,\n\t\t\t\t\tdecision,\n\t\t\t\t\t// No rollback plan on a failed apply — nothing durable landed, so there is nothing to undo.\n\t\t\t\t\trollback: writeFailed ? undefined : rollback,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).\n\t\t// Idempotent on reportId so a retried/duplicated pass cannot double-count.\n\t\tif (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {\n\t\t\tthis.deps.addSpawnedUsage(result.usage, { label: \"reflection\", reportId: input.reportId });\n\t\t}\n\t\treturn result;\n\t}\n\n\tgetLearningAuditRecords(): LearningAuditRecord[] {\n\t\treturn getLearningAuditSnapshots(this.deps.getSessionManager().getEntries());\n\t}\n\n\t/**\n\t * Roll back one applied durable learning change by executing the inverse operation recorded in\n\t * its audit record (memory ops run through the same bundled memory-tool path as the original\n\t * apply; promoted skills are archived). Appends a linked \"rollback\" audit record on success so\n\t * the change history stays complete and a change cannot be rolled back twice.\n\t */\n\tasync rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string }> {\n\t\tif (this.deps.isDisposed()) return { ok: false, reason: \"session_disposed\" };\n\n\t\tconst audits = this.getLearningAuditRecords();\n\t\tconst audit = audits.find((record) => record.id === auditId);\n\t\tif (!audit) return { ok: false, reason: \"audit_not_found\" };\n\t\tif (audit.action !== \"apply\") return { ok: false, reason: \"not_an_applied_change\" };\n\t\tif (audits.some((record) => record.action === \"rollback\" && record.rollbackOf === auditId)) {\n\t\t\treturn { ok: false, reason: \"already_rolled_back\" };\n\t\t}\n\t\tconst rollback = audit.rollback;\n\t\tif (!rollback) return { ok: false, reason: \"no_rollback_plan\" };\n\n\t\t// Every inverse must be VERIFIED-applied before the rollback audit is appended: a silently\n\t\t// failed inverse that still recorded \"rollback\" would permanently self-lock the change\n\t\t// behind already_rolled_back while the durable write is in fact still live.\n\t\tswitch (rollback.kind) {\n\t\t\tcase \"memory_remove\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!(await this._applyReflectionWrite({ kind: \"memory_remove\", target: rollback.target }))) {\n\t\t\t\t\treturn { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_restore\": {\n\t\t\t\tif (!rollback.target || rollback.previous === undefined) {\n\t\t\t\t\treturn { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\t}\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_replace\",\n\t\t\t\t\ttarget: rollback.target,\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_add\": {\n\t\t\t\tif (rollback.previous === undefined) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_add\",\n\t\t\t\t\tsection: \"MEMORY\",\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"archive_skill\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!this.deps.archivePromotedSkill(rollback.target)) {\n\t\t\t\t\treturn { ok: false, reason: \"skill_archive_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\tid: `${audit.id}-rollback`,\n\t\t\tproposalId: audit.proposalId,\n\t\t\tlayer: audit.layer,\n\t\t\taction: \"rollback\",\n\t\t\tsummary: `Rolled back: ${audit.summary}`,\n\t\t\treasonCode: \"user_requested_rollback\",\n\t\t\tdecision: audit.decision,\n\t\t\trollbackOf: audit.id,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t});\n\t\treturn { ok: true, reason: \"rollback_applied\" };\n\t}\n\n\t/**\n\t * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`\n\t * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring\n\t * isn't found there. Never throws (reflection must never break a turn); returns whether the write\n\t * actually applied so callers that MUST know — rollback's once-only accounting — can react instead\n\t * of recording a success that never happened.\n\t */\n\tprivate async _applyReflectionWrite(write: ReflectionWrite, signal?: AbortSignal): Promise<boolean> {\n\t\t// R7 memory-to-behavior: a recurring procedure is compiled into an executable skill file rather\n\t\t// than stored as a flat fact. Written under the agent skills dir so it loads like any user skill.\n\t\tif (write.kind === \"promote_skill\") {\n\t\t\treturn this._promoteReflectionSkill(write.name, write.description, write.body);\n\t\t}\n\n\t\ttype MemResult = { details?: { success?: boolean; error?: string } };\n\t\ttype MemExec = (\n\t\t\ttoolCallId: string,\n\t\t\tparams: { action: string; target: string; content?: string; oldContent?: string },\n\t\t\tsignal: AbortSignal | undefined,\n\t\t\tonUpdate: undefined,\n\t\t\tctx: undefined,\n\t\t) => Promise<MemResult>;\n\t\tconst memTool = this.deps\n\t\t\t.getMemoryManager()\n\t\t\t.getToolDefinitions()\n\t\t\t.find((t) => t.name === \"memory\");\n\t\tconst exec = memTool?.execute as unknown as MemExec | undefined;\n\t\tif (!exec) return false;\n\n\t\tconst run = (params: Parameters<MemExec>[1]) => exec(\"reflection\", params, signal, undefined, undefined);\n\n\t\tif (write.kind === \"memory_add\") {\n\t\t\ttry {\n\t\t\t\tconst res = await run({\n\t\t\t\t\taction: \"add\",\n\t\t\t\t\ttarget: write.section === \"USER\" ? \"user\" : \"memory\",\n\t\t\t\t\tcontent: write.text,\n\t\t\t\t});\n\t\t\t\treturn res?.details?.success === true;\n\t\t\t} catch {\n\t\t\t\t// best-effort; reflection writes must never throw into the turn loop\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports\n\t\t// outcomes via `details.success` (it catches its own errors rather than throwing). Only a\n\t\t// genuine \"not found in the file\" justifies trying the other file; a real failure for a file\n\t\t// (budget exceeded / drift) must NOT fall through and mutate the wrong target.\n\t\tfor (const target of [\"memory\", \"user\"] as const) {\n\t\t\ttry {\n\t\t\t\tconst params =\n\t\t\t\t\twrite.kind === \"memory_replace\"\n\t\t\t\t\t\t? { action: \"replace\", target, oldContent: write.target, content: write.text }\n\t\t\t\t\t\t: { action: \"remove\", target, oldContent: write.target };\n\t\t\t\tconst res = await run(params);\n\t\t\t\tif (res?.details?.success === true) return true; // applied\n\t\t\t\tif (!/not found/i.test(String(res?.details?.error ?? \"\"))) return false; // real failure — don't misapply\n\t\t\t\t// substring simply absent from this file — try the next target\n\t\t\t} catch {\n\t\t\t\t// defensive: if the tool ever does throw, try the next target\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any\n\t * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.\n\t */\n\tprivate _promoteReflectionSkill(rawName: string, description: string, body: string): boolean {\n\t\tconst name = rawName\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\")\n\t\t\t.slice(0, 64);\n\t\tif (!name || !body.trim()) return false;\n\t\ttry {\n\t\t\tconst dir = join(this.deps.getAgentDir(), \"skills\", name);\n\t\t\tconst file = join(dir, \"SKILL.md\");\n\t\t\tif (existsSync(file)) return false; // do not overwrite an existing skill\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\tconst safeDescription = description.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t\t// `promoted: true` marks this as reflection-generated so the curator (#32) can lifecycle-manage\n\t\t\t// it (archive/consolidate) WITHOUT ever touching hand-authored user skills.\n\t\t\tconst content = `---\\nname: ${name}\\ndescription: ${safeDescription}\\npromoted: true\\n---\\n\\n<!-- Auto-generated by the reflection engine (R7 memory-to-behavior). Review and refine. -->\\n\\n${body.trim()}\\n`;\n\t\t\twriteFileSync(file, content, \"utf-8\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// promotion must never break a turn\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"reflection-controller.js","sourceRoot":"","sources":["../../src/core/reflection-controller.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Native reflection + learning-write controller.\n *\n * Extracted verbatim from agent-session.ts (god-file decomposition). Owns the end-of-loop reflection\n * pass (R2), the isolated-completion primitive it runs on, and the learning-apply/rollback path that\n * turns reflection writes into gated, audited durable memory/skill changes. It mutates NO session\n * fields — every durable effect goes through the bundled memory tool, the session log (via deps), or\n * the skills dir; the whole pass is best-effort and never throws into the turn loop. Reads live\n * session state (model/agent/registry/memory/settings) through narrow deps accessors rather than the\n * whole AgentSession.\n */\n\nimport { existsSync, mkdirSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport {\n\ttype Agent,\n\ttype AgentContext,\n\ttype AgentLoopConfig,\n\trunAgentLoop,\n\ttype ThinkingLevel,\n} from \"@caupulican/pi-agent-core\";\nimport type { SessionManager } from \"@caupulican/pi-agent-core/node\";\nimport type { Api, AssistantMessage, Context, Model, SimpleStreamOptions, TextContent, Usage } from \"@caupulican/pi-ai\";\nimport type { IsolatedCompletionOptions, IsolatedCompletionResult } from \"./agent-session.ts\";\nimport type { LearningDecision } from \"./autonomy/contracts.ts\";\nimport { AUTONOMY_TELEMETRY_EVENT_TYPES, type AutonomyTelemetryEvent } from \"./autonomy/telemetry-events.ts\";\nimport {\n\tAPPLY_WRITE_REFUSED_REASON_CODE,\n\tappendLearningAuditSnapshot,\n\tcontradictionsForReflectionWrite,\n\tgetLearningAuditSnapshots,\n\ttype LearningAuditRecord,\n\tproposalFromReflectionWrite,\n\trollbackPlanForReflectionWrite,\n} from \"./learning/learning-audit.ts\";\nimport { evaluateLearningDecision } from \"./learning/learning-gate.ts\";\nimport { ObservationStore, observationKey } from \"./learning/observation-store.ts\";\nimport {\n\ttype DemandSignals,\n\tdecideDemand,\n\tReflectionEngine,\n\ttype ReflectionResult,\n\ttype ReflectionWrite,\n} from \"./learning/reflection-engine.ts\";\nimport type { MemoryManager } from \"./memory/memory-manager.ts\";\nimport type { ModelRegistry } from \"./model-registry.ts\";\nimport type { SettingsManager } from \"./settings-manager.ts\";\n\nexport interface ReflectionControllerDeps {\n\t/** Current session model (fallback for an isolated call that omits its own model). */\n\tgetModel(): Model<any> | undefined;\n\t/** The underlying agent — its `streamFn` runs the isolated completion. */\n\tgetAgent(): Agent;\n\t/** True when the session's stream fn is the raw `streamSimple` provider (auth must be injected). */\n\tisRawStreamSimple(): boolean;\n\t/** Model registry for API-key/header resolution on the raw-provider path. */\n\tgetModelRegistry(): ModelRegistry;\n\t/** Memory subsystem — the bundled `memory` tool applies durable writes; fresh block feeds reflection. */\n\tgetMemoryManager(): MemoryManager;\n\t/** Settings — the learning-apply policy (gate thresholds, auto-apply layers) is read here. */\n\tgetSettingsManager(): SettingsManager;\n\t/** Session log — audit snapshots and learning-audit reads go through this. */\n\tgetSessionManager(): SessionManager;\n\t/** Agent dir — reflection-promoted skills are written under `<agentDir>/skills/`. */\n\tgetAgentDir(): string;\n\t/** Child sessions must not learn — the pass returns null for them. */\n\tisChildSession(): boolean;\n\t/** Disposal short-circuits: no completion, no writes against a dead session. */\n\tisDisposed(): boolean;\n\t/** Session-lifetime abort signal — aborts an in-flight reflection completion on dispose. */\n\tgetReflectionSignal(): AbortSignal;\n\t/** Archive a promoted skill (rollback of a `promote_skill` write). */\n\tarchivePromotedSkill(name: string): boolean;\n\t/** G3/G8 autonomy telemetry sink for learning-gate outcomes and approval requests. */\n\temitAutonomyTelemetry(event: AutonomyTelemetryEvent): void;\n\t/** Account the reflection pass's token spend into the cost roll-up (idempotent on reportId). */\n\taddSpawnedUsage(\n\t\tusage: Usage,\n\t\topts?: { label?: string; sourceSessionId?: string; reportId?: string },\n\t): string | undefined;\n\t/** Persist a learning-gate decision snapshot to the session log. */\n\tsaveLearningDecisionSnapshot(decision: LearningDecision): string;\n\t/** Resolve text-tool fallback for the selected isolated model, not the foreground model. */\n\tresolveTextToolCallProtocol(model: Model<Api>): SimpleStreamOptions[\"textToolCallProtocol\"];\n\t/** Ensure a managed-local model is running/resident before any isolated lane calls it. */\n\tensureModelReady(model: Model<any>): Promise<void>;\n}\n\nexport class ReflectionController {\n\tprivate readonly deps: ReflectionControllerDeps;\n\n\tconstructor(deps: ReflectionControllerDeps) {\n\t\tthis.deps = deps;\n\t}\n\n\t/**\n\t * Run an LLM completion fully ISOLATED from the main session — the load-bearing primitive for\n\t * reflection and bounded child lanes (adaptive-agent design §6c/§7).\n\t *\n\t * Isolation invariants (audited by codex): builds fresh context (no main history), defaults to no\n\t * tools, and passes **no `sessionId`**. A tool-enabled call receives only caller-owned tools and\n\t * hooks, and is turn-bounded. It cannot mutate `agent.state.messages`, append session entries, or\n\t * touch the foreground tool registry. Mirrors `generateSummary()`'s one-shot mechanics otherwise.\n\t *\n\t * Returns the result even on an error/aborted stop reason (callers — e.g. a background reflection\n\t * microtask — decide whether to act); it does not throw on a model-level error.\n\t */\n\tasync runIsolatedCompletion(opts: IsolatedCompletionOptions): Promise<IsolatedCompletionResult> {\n\t\tconst model = opts.model ?? this.deps.getModel();\n\t\tif (!model) {\n\t\t\tthrow new Error(\"runIsolatedCompletion: no model available\");\n\t\t}\n\t\tawait this.deps.ensureModelReady(model);\n\t\tconst thinkingLevel = opts.thinkingLevel ?? \"off\";\n\n\t\t// Fresh, isolated context: explicit messages, caller-owned tools only, nothing from the main session.\n\t\tconst context: Context = {\n\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\tmessages: opts.messages,\n\t\t\ttools: opts.tools ?? [],\n\t\t};\n\n\t\t// Isolate the prompt cache and DELIBERATELY omit sessionId so no session-aware caching/routing\n\t\t// can entangle this call with the main session.\n\t\tconst options: SimpleStreamOptions = {\n\t\t\tmaxTokens: opts.maxTokens,\n\t\t\tsignal: opts.signal,\n\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\treasoning: thinkingLevel,\n\t\t};\n\n\t\t// When streamFn is the raw streamSimple (e.g. in tests), auth must be injected explicitly.\n\t\t// Throw only when auth genuinely fails — providers that authenticate without an API key\n\t\t// (OAuth, local no-key) legitimately return ok with an undefined apiKey.\n\t\tif (this.deps.isRawStreamSimple()) {\n\t\t\tconst auth = await this.deps.getModelRegistry().getApiKeyAndHeaders(model);\n\t\t\tif (!auth.ok) {\n\t\t\t\tthrow new Error(auth.error);\n\t\t\t}\n\t\t\toptions.apiKey = auth.apiKey;\n\t\t\toptions.headers = auth.headers;\n\t\t}\n\n\t\tif (opts.tools && opts.tools.length > 0) {\n\t\t\tconst agent = this.deps.getAgent();\n\t\t\tconst foregroundModel = agent.state.model;\n\t\t\tconst usesForegroundModel = foregroundModel?.provider === model.provider && foregroundModel.id === model.id;\n\t\t\tconst textToolCallProtocol = this.deps.resolveTextToolCallProtocol(model);\n\t\t\tconst requestedMaxTurns =\n\t\t\t\ttypeof opts.maxTurns === \"number\" && Number.isFinite(opts.maxTurns) ? Math.floor(opts.maxTurns) : 6;\n\t\t\tconst maxTurns = Math.max(1, Math.min(12, requestedMaxTurns));\n\t\t\tlet completedTurns = 0;\n\t\t\tconst childContext: AgentContext = {\n\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\tmessages: [],\n\t\t\t\ttools: [...opts.tools],\n\t\t\t};\n\t\t\tconst loopConfig: AgentLoopConfig = {\n\t\t\t\tmodel,\n\t\t\t\tmaxTokens: opts.maxTokens,\n\t\t\t\tcacheRetention: opts.cacheRetention ?? \"none\",\n\t\t\t\treasoning: thinkingLevel,\n\t\t\t\t...(options.apiKey !== undefined ? { apiKey: options.apiKey } : {}),\n\t\t\t\t...(options.headers !== undefined ? { headers: options.headers } : {}),\n\t\t\t\ttemperature: textToolCallProtocol ? 0 : undefined,\n\t\t\t\ttextToolCallProtocol,\n\t\t\t\tonTextToolProtocolParse: usesForegroundModel ? agent.onTextToolProtocolParse : undefined,\n\t\t\t\ttransport: agent.transport,\n\t\t\t\tthinkingBudgets: agent.thinkingBudgets,\n\t\t\t\tmaxRetryDelayMs: agent.maxRetryDelayMs,\n\t\t\t\tmaxStallTurns: Math.max(2, Math.min(maxTurns, agent.maxStallTurns ?? maxTurns)),\n\t\t\t\ttoolExecution: \"sequential\",\n\t\t\t\ttoolArgumentTeachEnabled: agent.toolArgumentTeachEnabled,\n\t\t\t\tonToolArgumentValidation: usesForegroundModel ? agent.onToolArgumentValidation : undefined,\n\t\t\t\ttoolValidationEscalationThreshold: agent.toolValidationEscalationThreshold,\n\t\t\t\tonToolValidationEscalation: usesForegroundModel ? agent.onToolValidationEscalation : undefined,\n\t\t\t\tbeforeToolCall: opts.beforeToolCall,\n\t\t\t\tafterToolCall: opts.afterToolCall,\n\t\t\t\tshouldStopAfterTurn: () => {\n\t\t\t\t\tcompletedTurns += 1;\n\t\t\t\t\treturn completedTurns >= maxTurns;\n\t\t\t\t},\n\t\t\t\tconvertToLlm: agent.convertToLlm,\n\t\t\t};\n\t\t\tconst messages = await runAgentLoop(\n\t\t\t\topts.messages,\n\t\t\t\tchildContext,\n\t\t\t\tloopConfig,\n\t\t\t\t() => {},\n\t\t\t\topts.signal,\n\t\t\t\tagent.streamFn,\n\t\t\t);\n\t\t\tconst assistantMessages = messages.filter(\n\t\t\t\t(message): message is AssistantMessage => message.role === \"assistant\",\n\t\t\t);\n\t\t\tlet finalAssistant = assistantMessages.at(-1);\n\t\t\tif (!finalAssistant) {\n\t\t\t\tthrow new Error(\"runIsolatedCompletion: child loop produced no assistant message\");\n\t\t\t}\n\n\t\t\tconst hasFinalText = finalAssistant.content.some(\n\t\t\t\t(content) => content.type === \"text\" && content.text.trim().length > 0,\n\t\t\t);\n\t\t\tconst endedOnToolCall = finalAssistant.content.some((content) => content.type === \"toolCall\");\n\t\t\tif (opts.finalTextPrompt && !hasFinalText && endedOnToolCall && !opts.signal?.aborted) {\n\t\t\t\t// A hard turn bound may stop immediately after successful tool execution. Preserve that bound,\n\t\t\t\t// then allow exactly one tool-free synthesis call so the gathered work is not thrown away.\n\t\t\t\tconst finalizationMessages = await agent.convertToLlm(messages);\n\t\t\t\tconst finalizationStream = await agent.streamFn(\n\t\t\t\t\tmodel,\n\t\t\t\t\t{\n\t\t\t\t\t\tsystemPrompt: opts.systemPrompt,\n\t\t\t\t\t\tmessages: [\n\t\t\t\t\t\t\t...finalizationMessages,\n\t\t\t\t\t\t\t{ role: \"user\", content: opts.finalTextPrompt, timestamp: Date.now() },\n\t\t\t\t\t\t],\n\t\t\t\t\t\ttools: [],\n\t\t\t\t\t},\n\t\t\t\t\toptions,\n\t\t\t\t);\n\t\t\t\tfinalAssistant = await finalizationStream.result();\n\t\t\t\tassistantMessages.push(finalAssistant);\n\t\t\t}\n\n\t\t\tconst usage = assistantMessages.reduce<Usage>(\n\t\t\t\t(total, message) => ({\n\t\t\t\t\tinput: total.input + message.usage.input,\n\t\t\t\t\toutput: total.output + message.usage.output,\n\t\t\t\t\tcacheRead: total.cacheRead + message.usage.cacheRead,\n\t\t\t\t\tcacheWrite: total.cacheWrite + message.usage.cacheWrite,\n\t\t\t\t\ttotalTokens: total.totalTokens + message.usage.totalTokens,\n\t\t\t\t\tcost: {\n\t\t\t\t\t\tinput: total.cost.input + message.usage.cost.input,\n\t\t\t\t\t\toutput: total.cost.output + message.usage.cost.output,\n\t\t\t\t\t\tcacheRead: total.cost.cacheRead + message.usage.cost.cacheRead,\n\t\t\t\t\t\tcacheWrite: total.cost.cacheWrite + message.usage.cost.cacheWrite,\n\t\t\t\t\t\ttotal: total.cost.total + message.usage.cost.total,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{\n\t\t\t\t\tinput: 0,\n\t\t\t\t\toutput: 0,\n\t\t\t\t\tcacheRead: 0,\n\t\t\t\t\tcacheWrite: 0,\n\t\t\t\t\ttotalTokens: 0,\n\t\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t\t},\n\t\t\t);\n\t\t\tconst text = finalAssistant.content\n\t\t\t\t.filter((content): content is TextContent => content.type === \"text\")\n\t\t\t\t.map((content) => content.text)\n\t\t\t\t.join(\"\");\n\t\t\treturn { text, usage, stopReason: finalAssistant.stopReason };\n\t\t}\n\n\t\tconst stream = await this.deps.getAgent().streamFn(model, context, options);\n\t\tconst result = await stream.result();\n\t\tconst text = result.content\n\t\t\t.filter((c): c is TextContent => c.type === \"text\")\n\t\t\t.map((c) => c.text)\n\t\t\t.join(\"\");\n\t\tconst usage: Usage = result.usage ?? {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\ttotalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t};\n\t\treturn { text, usage, stopReason: result.stopReason };\n\t}\n\n\t/**\n\t * Native end-of-loop reflection pass (R2). Demand-gates (zero-I/O), and when warranted runs the\n\t * {@link ReflectionEngine} via an isolated completion ({@link runIsolatedCompletion}), applies the\n\t * resulting memory writes through the bundled `memory` tool, and accounts the reflection's token\n\t * cost via the cost-aggregation surface so it stays visible and net-negative-auditable.\n\t *\n\t * Returns `null` when the gate skips (or in a child session, which must not learn). The whole pass\n\t * is best-effort: a model/parse error yields no writes, never throws into the caller.\n\t */\n\tasync runReflectionPass(input: {\n\t\tsignals: DemandSignals;\n\t\trecentTurnText: string;\n\t\tmodel?: Model<any>;\n\t\tthinkingLevel?: ThinkingLevel;\n\t\tsignal?: AbortSignal;\n\t\t/** Stable id so a duplicate scheduling/retry of the same pass can't double-count its cost. */\n\t\treportId?: string;\n\t}): Promise<ReflectionResult | null> {\n\t\tif (this.deps.isChildSession() || this.deps.isDisposed()) return null;\n\t\tconst plan = decideDemand(input.signals);\n\t\tif (plan.act === \"skip\") return null;\n\n\t\t// Bug #21: tie this background pass to the session lifetime. Disposing the session aborts the\n\t\t// in-flight completion (input.signal can add a more specific abort).\n\t\tconst signal = input.signal\n\t\t\t? AbortSignal.any([input.signal, this.deps.getReflectionSignal()])\n\t\t\t: this.deps.getReflectionSignal();\n\n\t\tconst complete = (systemPrompt: string, userPrompt: string) =>\n\t\t\tthis.runIsolatedCompletion({\n\t\t\t\tsystemPrompt,\n\t\t\t\tmessages: [{ role: \"user\", content: [{ type: \"text\", text: userPrompt }], timestamp: Date.now() }],\n\t\t\t\tmodel: input.model,\n\t\t\t\tthinkingLevel: input.thinkingLevel ?? \"low\",\n\t\t\t\tmaxTokens: plan.tokenBudget,\n\t\t\t\tsignal,\n\t\t\t\t// The reflection system prompt is static (#33) — let the provider cache the prefix so\n\t\t\t\t// repeated passes only pay for the variable tail.\n\t\t\t\tcacheRetention: \"short\",\n\t\t\t});\n\n\t\tconst result = await new ReflectionEngine().reflect({\n\t\t\trecentTurnText: input.recentTurnText,\n\t\t\t// Read memory FRESH (not the prefix-cache-frozen system-prompt block) so confront-before-write\n\t\t\t// sees writes made earlier this session.\n\t\t\texistingMemory: this.deps.getMemoryManager().buildSystemPromptBlockFresh() || \"\",\n\t\t\tplan,\n\t\t\tcomplete,\n\t\t});\n\n\t\t// Bug #21: if the session was disposed while the completion was in flight, do NOT write memory\n\t\t// or skills against the dead session.\n\t\tif (this.deps.isDisposed()) return result;\n\n\t\t// Learning apply policy: every durable write is converted to a proposal, decided by the\n\t\t// learning gate, and audited with a rollback plan. With the policy disabled (default) the\n\t\t// legacy direct-apply behavior is preserved — but now leaves audit records with rollback info.\n\t\tconst policy = this.deps.getSettingsManager().getLearningPolicySettings();\n\t\t// The audit id sequence counts STORED snapshots only: it reseeds from the stored count on\n\t\t// every pass, so advancing it for a no-op (which stores nothing) would make later passes\n\t\t// reuse ids — and rollback keys on the id, so a collision blocks or misdirects rollback.\n\t\tlet auditSequence = getLearningAuditSnapshots(this.deps.getSessionManager().getEntries()).length;\n\t\t// G6 evidence strength: durable proposals accumulate observation counts across passes/sessions\n\t\t// so the gate can distinguish a one-off cue from a repeatedly-confirmed lesson. Built once per\n\t\t// pass; every increment is best-effort (store IO must never break reflection).\n\t\tconst observationStore = ObservationStore.forAgentDir(this.deps.getAgentDir());\n\t\tlet writeIndex = 0;\n\t\tfor (const write of result.writes) {\n\t\t\twriteIndex += 1;\n\t\t\tconst proposalId = `${input.reportId ?? \"reflection\"}-w${writeIndex}`;\n\t\t\tconst proposal = proposalFromReflectionWrite(write, proposalId);\n\t\t\tconst rollback = rollbackPlanForReflectionWrite(write);\n\t\t\tlet observations = 1;\n\t\t\tif (policy.enabled) {\n\t\t\t\ttry {\n\t\t\t\t\tobservations = observationStore.increment(observationKey(proposal.layer, proposal.summary));\n\t\t\t\t} catch {\n\t\t\t\t\t// A store read/write failure falls back to a fresh count of 1, which keeps the gate\n\t\t\t\t\t// proposal-first (never spuriously auto-applies) rather than crashing the pass.\n\t\t\t\t\tobservations = 1;\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst decision: LearningDecision = policy.enabled\n\t\t\t\t? evaluateLearningDecision({\n\t\t\t\t\t\tproposal,\n\t\t\t\t\t\tconfidence: policy.reflectionSourceConfidence,\n\t\t\t\t\t\tobservations,\n\t\t\t\t\t\t// A replace/remove supersedes an existing durable fact — the reflection engine's\n\t\t\t\t\t\t// confront-before-write conflict signal — so it routes through approval instead of\n\t\t\t\t\t\t// silently overwriting prior memory. Additive writes contradict nothing.\n\t\t\t\t\t\tcontradictions: contradictionsForReflectionWrite(write),\n\t\t\t\t\t\tsettings: {\n\t\t\t\t\t\t\tenabled: true,\n\t\t\t\t\t\t\tautoApplyEnabled: policy.autoApplyEnabled,\n\t\t\t\t\t\t\tconfidenceThreshold: policy.confidenceThreshold,\n\t\t\t\t\t\t\tminObservations: policy.minObservations,\n\t\t\t\t\t\t\tallowedAutoApplyLayers: policy.allowedAutoApplyLayers,\n\t\t\t\t\t\t\trequireRollbackPlan: policy.requireRollbackPlan,\n\t\t\t\t\t\t\tautoApplySupersessions: policy.autoApplySupersessions,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t: {\n\t\t\t\t\t\tkind: \"apply\",\n\t\t\t\t\t\treasonCode: \"learning_policy_disabled_legacy_apply\",\n\t\t\t\t\t\tconfidence: 0,\n\t\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\t\trequiresApproval: false,\n\t\t\t\t\t};\n\n\t\t\tthis.deps.saveLearningDecisionSnapshot(decision);\n\t\t\t// G3: learning-gate outcome. Codes/numbers only — never the proposal summary/memory text.\n\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.learningDecision,\n\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\tpayload: {\n\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\tconfidence: decision.confidence,\n\t\t\t\t\trequiresApproval: decision.requiresApproval,\n\t\t\t\t},\n\t\t\t});\n\t\t\t// G8: a proposal that needs human sign-off is an approval REQUEST. Codes/layer only —\n\t\t\t// never the proposal summary/memory text (those live only in the audit snapshot).\n\t\t\tif (decision.requiresApproval) {\n\t\t\t\tthis.deps.emitAutonomyTelemetry({\n\t\t\t\t\ttype: AUTONOMY_TELEMETRY_EVENT_TYPES.approvalRequest,\n\t\t\t\t\ttimestamp: new Date().toISOString(),\n\t\t\t\t\tpayload: {\n\t\t\t\t\t\tkind: decision.kind,\n\t\t\t\t\t\treasonCode: decision.reasonCode,\n\t\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\t// The gate's decision and the write's actual outcome are two different questions: the memory\n\t\t\t// tool can refuse a write (budget exceeded, drift, threat) via details.success:false without\n\t\t\t// throwing. Capture that outcome instead of assuming \"decision.kind === apply\" means it landed\n\t\t\t// — otherwise a refused write leaves a phantom \"apply\" audit whose rollback later fails\n\t\t\t// not-found (or, worse, misfires against whatever now occupies that text).\n\t\t\tconst applied = decision.kind === \"apply\" ? await this._applyReflectionWrite(write, signal) : false;\n\t\t\tconst writeFailed = decision.kind === \"apply\" && !applied;\n\t\t\tif (decision.kind !== \"no-op\") {\n\t\t\t\tauditSequence += 1;\n\t\t\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\t\t\tid: `audit-${auditSequence}`,\n\t\t\t\t\tproposalId,\n\t\t\t\t\tlayer: proposal.layer,\n\t\t\t\t\taction: writeFailed ? \"apply_failed\" : decision.kind === \"apply\" ? \"apply\" : \"propose\",\n\t\t\t\t\tsummary: proposal.summary,\n\t\t\t\t\treasonCode: writeFailed ? APPLY_WRITE_REFUSED_REASON_CODE : decision.reasonCode,\n\t\t\t\t\tdecision,\n\t\t\t\t\t// No rollback plan on a failed apply — nothing durable landed, so there is nothing to undo.\n\t\t\t\t\trollback: writeFailed ? undefined : rollback,\n\t\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// Account the reflection's spend so it surfaces in the footer roll-up (net-token visibility).\n\t\t// Idempotent on reportId so a retried/duplicated pass cannot double-count.\n\t\tif (result.usage.cost.total > 0 || result.usage.totalTokens > 0) {\n\t\t\tthis.deps.addSpawnedUsage(result.usage, { label: \"reflection\", reportId: input.reportId });\n\t\t}\n\t\treturn result;\n\t}\n\n\tgetLearningAuditRecords(): LearningAuditRecord[] {\n\t\treturn getLearningAuditSnapshots(this.deps.getSessionManager().getEntries());\n\t}\n\n\t/**\n\t * Roll back one applied durable learning change by executing the inverse operation recorded in\n\t * its audit record (memory ops run through the same bundled memory-tool path as the original\n\t * apply; promoted skills are archived). Appends a linked \"rollback\" audit record on success so\n\t * the change history stays complete and a change cannot be rolled back twice.\n\t */\n\tasync rollbackLearningWrite(auditId: string): Promise<{ ok: boolean; reason: string }> {\n\t\tif (this.deps.isDisposed()) return { ok: false, reason: \"session_disposed\" };\n\n\t\tconst audits = this.getLearningAuditRecords();\n\t\tconst audit = audits.find((record) => record.id === auditId);\n\t\tif (!audit) return { ok: false, reason: \"audit_not_found\" };\n\t\tif (audit.action !== \"apply\") return { ok: false, reason: \"not_an_applied_change\" };\n\t\tif (audits.some((record) => record.action === \"rollback\" && record.rollbackOf === auditId)) {\n\t\t\treturn { ok: false, reason: \"already_rolled_back\" };\n\t\t}\n\t\tconst rollback = audit.rollback;\n\t\tif (!rollback) return { ok: false, reason: \"no_rollback_plan\" };\n\n\t\t// Every inverse must be VERIFIED-applied before the rollback audit is appended: a silently\n\t\t// failed inverse that still recorded \"rollback\" would permanently self-lock the change\n\t\t// behind already_rolled_back while the durable write is in fact still live.\n\t\tswitch (rollback.kind) {\n\t\t\tcase \"memory_remove\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!(await this._applyReflectionWrite({ kind: \"memory_remove\", target: rollback.target }))) {\n\t\t\t\t\treturn { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_restore\": {\n\t\t\t\tif (!rollback.target || rollback.previous === undefined) {\n\t\t\t\t\treturn { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\t}\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_replace\",\n\t\t\t\t\ttarget: rollback.target,\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"memory_add\": {\n\t\t\t\tif (rollback.previous === undefined) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tconst applied = await this._applyReflectionWrite({\n\t\t\t\t\tkind: \"memory_add\",\n\t\t\t\t\tsection: \"MEMORY\",\n\t\t\t\t\ttext: rollback.previous,\n\t\t\t\t});\n\t\t\t\tif (!applied) return { ok: false, reason: \"rollback_apply_failed\" };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"archive_skill\": {\n\t\t\t\tif (!rollback.target) return { ok: false, reason: \"missing_rollback_target\" };\n\t\t\t\tif (!this.deps.archivePromotedSkill(rollback.target)) {\n\t\t\t\t\treturn { ok: false, reason: \"skill_archive_failed\" };\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tappendLearningAuditSnapshot(this.deps.getSessionManager(), {\n\t\t\tid: `${audit.id}-rollback`,\n\t\t\tproposalId: audit.proposalId,\n\t\t\tlayer: audit.layer,\n\t\t\taction: \"rollback\",\n\t\t\tsummary: `Rolled back: ${audit.summary}`,\n\t\t\treasonCode: \"user_requested_rollback\",\n\t\t\tdecision: audit.decision,\n\t\t\trollbackOf: audit.id,\n\t\t\tcreatedAt: new Date().toISOString(),\n\t\t});\n\t\treturn { ok: true, reason: \"rollback_applied\" };\n\t}\n\n\t/**\n\t * Apply one reflection write through the bundled `memory` tool. `memory_replace`/`memory_remove`\n\t * don't carry a target file, so we try MEMORY.md first and fall back to USER.md when the substring\n\t * isn't found there. Never throws (reflection must never break a turn); returns whether the write\n\t * actually applied so callers that MUST know — rollback's once-only accounting — can react instead\n\t * of recording a success that never happened.\n\t */\n\tprivate async _applyReflectionWrite(write: ReflectionWrite, signal?: AbortSignal): Promise<boolean> {\n\t\t// R7 memory-to-behavior: a recurring procedure is compiled into an executable skill file rather\n\t\t// than stored as a flat fact. Written under the agent skills dir so it loads like any user skill.\n\t\tif (write.kind === \"promote_skill\") {\n\t\t\treturn this._promoteReflectionSkill(write.name, write.description, write.body);\n\t\t}\n\n\t\ttype MemResult = { details?: { success?: boolean; error?: string } };\n\t\ttype MemExec = (\n\t\t\ttoolCallId: string,\n\t\t\tparams: { action: string; target: string; content?: string; oldContent?: string },\n\t\t\tsignal: AbortSignal | undefined,\n\t\t\tonUpdate: undefined,\n\t\t\tctx: undefined,\n\t\t) => Promise<MemResult>;\n\t\tconst memTool = this.deps\n\t\t\t.getMemoryManager()\n\t\t\t.getToolDefinitions()\n\t\t\t.find((t) => t.name === \"memory\");\n\t\tconst exec = memTool?.execute as unknown as MemExec | undefined;\n\t\tif (!exec) return false;\n\n\t\tconst run = (params: Parameters<MemExec>[1]) => exec(\"reflection\", params, signal, undefined, undefined);\n\n\t\tif (write.kind === \"memory_add\") {\n\t\t\ttry {\n\t\t\t\tconst res = await run({\n\t\t\t\t\taction: \"add\",\n\t\t\t\t\ttarget: write.section === \"USER\" ? \"user\" : \"memory\",\n\t\t\t\t\tcontent: write.text,\n\t\t\t\t});\n\t\t\t\treturn res?.details?.success === true;\n\t\t\t} catch {\n\t\t\t\t// best-effort; reflection writes must never throw into the turn loop\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\n\t\t// replace / remove carry no target file — try MEMORY.md, then USER.md. The memory tool reports\n\t\t// outcomes via `details.success` (it catches its own errors rather than throwing). Only a\n\t\t// genuine \"not found in the file\" justifies trying the other file; a real failure for a file\n\t\t// (budget exceeded / drift) must NOT fall through and mutate the wrong target.\n\t\tfor (const target of [\"memory\", \"user\"] as const) {\n\t\t\ttry {\n\t\t\t\tconst params =\n\t\t\t\t\twrite.kind === \"memory_replace\"\n\t\t\t\t\t\t? { action: \"replace\", target, oldContent: write.target, content: write.text }\n\t\t\t\t\t\t: { action: \"remove\", target, oldContent: write.target };\n\t\t\t\tconst res = await run(params);\n\t\t\t\tif (res?.details?.success === true) return true; // applied\n\t\t\t\tif (!/not found/i.test(String(res?.details?.error ?? \"\"))) return false; // real failure — don't misapply\n\t\t\t\t// substring simply absent from this file — try the next target\n\t\t\t} catch {\n\t\t\t\t// defensive: if the tool ever does throw, try the next target\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * R7: write a reflection-promoted skill as `<agentDir>/skills/<name>/SKILL.md` so it loads like any\n\t * user skill. Best-effort; never clobbers an existing (hand-authored) skill of the same name.\n\t */\n\tprivate _promoteReflectionSkill(rawName: string, description: string, body: string): boolean {\n\t\tconst name = rawName\n\t\t\t.trim()\n\t\t\t.toLowerCase()\n\t\t\t.replace(/[^a-z0-9-]+/g, \"-\")\n\t\t\t.replace(/^-+|-+$/g, \"\")\n\t\t\t.slice(0, 64);\n\t\tif (!name || !body.trim()) return false;\n\t\ttry {\n\t\t\tconst dir = join(this.deps.getAgentDir(), \"skills\", name);\n\t\t\tconst file = join(dir, \"SKILL.md\");\n\t\t\tif (existsSync(file)) return false; // do not overwrite an existing skill\n\t\t\tmkdirSync(dir, { recursive: true });\n\t\t\tconst safeDescription = description.replace(/[\\r\\n]+/g, \" \").trim();\n\t\t\t// `promoted: true` marks this as reflection-generated so the curator (#32) can lifecycle-manage\n\t\t\t// it (archive/consolidate) WITHOUT ever touching hand-authored user skills.\n\t\t\tconst content = `---\\nname: ${name}\\ndescription: ${safeDescription}\\npromoted: true\\n---\\n\\n<!-- Auto-generated by the reflection engine (R7 memory-to-behavior). Review and refine. -->\\n\\n${body.trim()}\\n`;\n\t\t\twriteFileSync(file, content, \"utf-8\");\n\t\t\treturn true;\n\t\t} catch {\n\t\t\t// promotion must never break a turn\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}
|