comisai 1.0.14 → 1.0.16
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/node_modules/@comis/agent/dist/bootstrap/index.d.ts +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/index.js +1 -1
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.d.ts +26 -0
- package/node_modules/@comis/agent/dist/bootstrap/system-prompt-assembler.js +86 -54
- package/node_modules/@comis/agent/dist/bootstrap/types.d.ts +7 -1
- package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.d.ts +12 -0
- package/node_modules/@comis/agent/dist/bootstrap/workspace-loader.js +21 -0
- package/node_modules/@comis/agent/dist/bridge/pi-event-bridge.js +12 -11
- package/node_modules/@comis/agent/dist/context-engine/constants.d.ts +11 -0
- package/node_modules/@comis/agent/dist/context-engine/constants.js +11 -0
- package/node_modules/@comis/agent/dist/context-engine/index.d.ts +1 -1
- package/node_modules/@comis/agent/dist/context-engine/index.js +1 -1
- package/node_modules/@comis/agent/dist/context-engine/llm-compaction.js +32 -18
- package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/cache-break-diff-writer.js +36 -0
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.d.ts +12 -0
- package/node_modules/@comis/agent/dist/executor/executor-post-execution.js +117 -27
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.d.ts +1 -0
- package/node_modules/@comis/agent/dist/executor/executor-prompt-runner.js +16 -4
- package/node_modules/@comis/agent/dist/executor/executor-stream-setup.js +17 -1
- package/node_modules/@comis/agent/dist/executor/executor-tool-assembly.js +29 -1
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.d.ts +8 -1
- package/node_modules/@comis/agent/dist/executor/prompt-assembly.js +22 -5
- package/node_modules/@comis/agent/dist/executor/session-snapshot-cleanup.js +3 -1
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.d.ts +2 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/index.js +2 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.d.ts +9 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/request-body-injector.js +65 -3
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.d.ts +28 -0
- package/node_modules/@comis/agent/dist/executor/stream-wrappers/stub-filter-injector.js +63 -0
- package/node_modules/@comis/agent/dist/executor/tool-deferral.d.ts +18 -1
- package/node_modules/@comis/agent/dist/executor/tool-deferral.js +275 -133
- package/node_modules/@comis/agent/dist/executor/ttl-guard.d.ts +6 -0
- package/node_modules/@comis/agent/dist/executor/ttl-guard.js +8 -0
- package/node_modules/@comis/agent/dist/session/comis-session-manager.js +7 -2
- package/node_modules/@comis/agent/package.json +1 -1
- package/node_modules/@comis/channels/dist/shared/typing-controller.d.ts +7 -0
- package/node_modules/@comis/channels/dist/shared/typing-controller.js +33 -0
- package/node_modules/@comis/channels/package.json +1 -1
- package/node_modules/@comis/cli/dist/commands/daemon.js +116 -28
- package/node_modules/@comis/cli/dist/doctor/checks/channel-health.js +2 -2
- package/node_modules/@comis/cli/dist/wizard/index.d.ts +2 -3
- package/node_modules/@comis/cli/dist/wizard/index.js +2 -3
- package/node_modules/@comis/cli/dist/wizard/steps/10-write-config.js +1 -1
- package/node_modules/@comis/cli/dist/wizard/types.d.ts +1 -2
- package/node_modules/@comis/cli/dist/wizard/types.js +1 -2
- package/node_modules/@comis/cli/package.json +1 -1
- package/node_modules/@comis/core/dist/config/schema-agent.d.ts +64 -10
- package/node_modules/@comis/core/dist/config/schema-agent.js +19 -0
- package/node_modules/@comis/core/dist/config/schema-skills.d.ts +16 -2
- package/node_modules/@comis/core/dist/config/schema-skills.js +9 -2
- package/node_modules/@comis/core/dist/config/schema.d.ts +19 -11
- package/node_modules/@comis/core/dist/event-bus/events-infra.d.ts +8 -0
- package/node_modules/@comis/core/package.json +1 -1
- package/node_modules/@comis/daemon/dist/daemon.js +2 -1
- package/node_modules/@comis/daemon/dist/wiring/setup-channels.js +19 -2
- package/node_modules/@comis/daemon/dist/wiring/setup-heartbeat.js +10 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-memory.d.ts +2 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-memory.js +15 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-schedulers.js +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.d.ts +1 -0
- package/node_modules/@comis/daemon/dist/wiring/setup-shutdown.js +4 -0
- package/node_modules/@comis/daemon/package.json +1 -1
- package/node_modules/@comis/gateway/package.json +1 -1
- package/node_modules/@comis/infra/package.json +1 -1
- package/node_modules/@comis/memory/package.json +1 -1
- package/node_modules/@comis/scheduler/dist/cron/cron-types.d.ts +11 -0
- package/node_modules/@comis/scheduler/dist/cron/cron-types.js +11 -0
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.d.ts +26 -1
- package/node_modules/@comis/scheduler/dist/heartbeat/agent-heartbeat-source.js +23 -2
- package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.d.ts +7 -0
- package/node_modules/@comis/scheduler/dist/heartbeat/heartbeat-config.js +2 -0
- package/node_modules/@comis/scheduler/package.json +1 -1
- package/node_modules/@comis/shared/package.json +1 -1
- package/node_modules/@comis/skills/dist/index.d.ts +1 -1
- package/node_modules/@comis/skills/dist/index.js +1 -1
- package/node_modules/@comis/skills/dist/policy/tool-policy.js +25 -0
- package/node_modules/@comis/skills/package.json +1 -1
- package/package.json +12 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type BootstrapFile, type TruncationResult, type PromptMode, type RuntimeInfo, type InboundMetadata, type BootstrapContextFile, SUBAGENT_BOOTSTRAP_ALLOWLIST, BOOTSTRAP_HEAD_RATIO, BOOTSTRAP_TAIL_RATIO, } from "./types.js";
|
|
2
|
-
export { loadWorkspaceBootstrapFiles, truncateFileContent, filterBootstrapFilesForSubAgent, filterBootstrapFilesForLightContext, filterBootstrapFilesForGroupChat, buildBootstrapContextFiles, } from "./workspace-loader.js";
|
|
2
|
+
export { loadWorkspaceBootstrapFiles, truncateFileContent, filterBootstrapFilesForSubAgent, filterBootstrapFilesForLightContext, filterBootstrapFilesForCron, filterBootstrapFilesForGroupChat, buildBootstrapContextFiles, } from "./workspace-loader.js";
|
|
3
3
|
export * from "./sections/index.js";
|
|
4
4
|
export { extractMarkdownSections, MAX_POST_COMPACTION_CHARS } from "./section-extractor.js";
|
|
5
5
|
export { assembleRichSystemPrompt, assembleRichSystemPromptBlocks, SECTION_SEPARATOR } from "./system-prompt-assembler.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Bootstrap — workspace file loading & system prompt assembly
|
|
3
3
|
export { SUBAGENT_BOOTSTRAP_ALLOWLIST, BOOTSTRAP_HEAD_RATIO, BOOTSTRAP_TAIL_RATIO, } from "./types.js";
|
|
4
|
-
export { loadWorkspaceBootstrapFiles, truncateFileContent, filterBootstrapFilesForSubAgent, filterBootstrapFilesForLightContext, filterBootstrapFilesForGroupChat, buildBootstrapContextFiles, } from "./workspace-loader.js";
|
|
4
|
+
export { loadWorkspaceBootstrapFiles, truncateFileContent, filterBootstrapFilesForSubAgent, filterBootstrapFilesForLightContext, filterBootstrapFilesForCron, filterBootstrapFilesForGroupChat, buildBootstrapContextFiles, } from "./workspace-loader.js";
|
|
5
5
|
export * from "./sections/index.js";
|
|
6
6
|
export { extractMarkdownSections, MAX_POST_COMPACTION_CHARS } from "./section-extractor.js";
|
|
7
7
|
export { assembleRichSystemPrompt, assembleRichSystemPromptBlocks, SECTION_SEPARATOR } from "./system-prompt-assembler.js";
|
|
@@ -82,6 +82,32 @@ export interface AssemblerParams {
|
|
|
82
82
|
/** Whether Silent Execution Planner (SEP) is enabled for this agent. */
|
|
83
83
|
sepEnabled?: boolean;
|
|
84
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Internal descriptor for a system prompt section.
|
|
87
|
+
*
|
|
88
|
+
* Engineering notes:
|
|
89
|
+
* - `id` must be unique across SECTIONS and stable (used by tests).
|
|
90
|
+
* - `includeIn` must be non-empty.
|
|
91
|
+
* - `build` receives `(params, mode)` and is responsible for forwarding
|
|
92
|
+
* `mode === "minimal"` as the existing builders' `isMinimal` parameter.
|
|
93
|
+
*
|
|
94
|
+
* @internal exported for tests only
|
|
95
|
+
*/
|
|
96
|
+
export interface SectionDescriptor {
|
|
97
|
+
readonly id: string;
|
|
98
|
+
readonly includeIn: ReadonlySet<PromptMode>;
|
|
99
|
+
readonly build: (params: AssemblerParams, mode: PromptMode) => string[];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Canonical section list in emission order.
|
|
103
|
+
*
|
|
104
|
+
* Order MUST match the previous hand-written `buildAllSections` so the
|
|
105
|
+
* `staticPrefix`/`attribution` index boundaries (2 + 2) continue to enclose
|
|
106
|
+
* identity+persona and safety+language respectively.
|
|
107
|
+
*
|
|
108
|
+
* @internal exported for tests only
|
|
109
|
+
*/
|
|
110
|
+
export declare const SECTIONS: ReadonlyArray<SectionDescriptor>;
|
|
85
111
|
/**
|
|
86
112
|
* Assemble a rich multi-section system prompt.
|
|
87
113
|
*
|
|
@@ -50,63 +50,92 @@ function joinSections(sections) {
|
|
|
50
50
|
const nonEmpty = sections.filter((lines) => lines.length > 0);
|
|
51
51
|
return nonEmpty.map((lines) => lines.join("\n")).join(SECTION_SEPARATOR);
|
|
52
52
|
}
|
|
53
|
+
/** Sections present in full, operational, and minimal modes.
|
|
54
|
+
* NB: builders may still self-filter to `[]` when their isMinimal flag is set. */
|
|
55
|
+
const MODES_ALL = new Set(["full", "operational", "minimal"]);
|
|
56
|
+
/** Sections present in full and minimal modes (stripped in operational).
|
|
57
|
+
* Interactive-only guidance that doesn't apply to autonomous cron/heartbeat runs
|
|
58
|
+
* but that minimal sub-agent contexts historically saw before this refactor.
|
|
59
|
+
* Minimal-mode builders typically self-filter to [] via their own isMinimal flag;
|
|
60
|
+
* membership here preserves pre-refactor behavior without changing minimal output. */
|
|
61
|
+
const MODES_FULL_MIN = new Set(["full", "minimal"]);
|
|
62
|
+
/** Sections present only in full mode (stripped in operational AND minimal).
|
|
63
|
+
* Reserved for sections whose builder is cheap but whose output is not desired
|
|
64
|
+
* in either non-interactive context. Currently unused; MODES_FULL_MIN is
|
|
65
|
+
* preferred to preserve pre-refactor minimal output. */
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- reserved for future use
|
|
67
|
+
const MODES_FULL = new Set(["full"]);
|
|
53
68
|
/**
|
|
54
|
-
*
|
|
69
|
+
* Canonical section list in emission order.
|
|
70
|
+
*
|
|
71
|
+
* Order MUST match the previous hand-written `buildAllSections` so the
|
|
72
|
+
* `staticPrefix`/`attribution` index boundaries (2 + 2) continue to enclose
|
|
73
|
+
* identity+persona and safety+language respectively.
|
|
74
|
+
*
|
|
75
|
+
* @internal exported for tests only
|
|
76
|
+
*/
|
|
77
|
+
export const SECTIONS = [
|
|
78
|
+
// --- Static prefix block (indices 0-1 in every mode that includes them) ---
|
|
79
|
+
{ id: "identity", includeIn: MODES_ALL, build: (p) => buildIdentitySection(p.agentName ?? "Comis") },
|
|
80
|
+
{ id: "persona", includeIn: MODES_ALL, build: (p) => buildPersonaSection(p.bootstrapFiles ?? []) },
|
|
81
|
+
// --- Attribution block (safety self-filters in minimal) ---
|
|
82
|
+
{ id: "safety", includeIn: MODES_ALL, build: (p, m) => buildSafetySection(m === "minimal") },
|
|
83
|
+
{ id: "language", includeIn: MODES_ALL, build: (p) => buildLanguageSection(p.userLanguage) },
|
|
84
|
+
// --- Semi-stable body: operational-kept sections (MODES_ALL -- builders self-filter for minimal) ---
|
|
85
|
+
{ id: "tooling", includeIn: MODES_ALL, build: (p, m) => buildToolingSection(p.toolNames ?? [], m === "minimal" ? "small" : "large", p.toolSummaries) },
|
|
86
|
+
{ id: "tool-call-style", includeIn: MODES_ALL, build: (p, m) => buildToolCallStyleSection(m === "minimal", p.toolNames ?? []) },
|
|
87
|
+
// --- Operational-stripped sections (MODES_FULL_MIN -- dropped in "operational") ---
|
|
88
|
+
{ id: "self-update", includeIn: MODES_FULL_MIN, build: (p, m) => buildSelfUpdateGatingSection(p.toolNames ?? [], m === "minimal", true) },
|
|
89
|
+
{ id: "config-secret", includeIn: MODES_FULL_MIN, build: (p, m) => buildConfigSecretIntegritySection(p.toolNames ?? [], m === "minimal") },
|
|
90
|
+
{ id: "privileged", includeIn: MODES_FULL_MIN, build: (p, m) => buildPrivilegedToolsSection(p.toolNames ?? [], m === "minimal", true) },
|
|
91
|
+
{ id: "compact-recover", includeIn: MODES_FULL_MIN, build: (p, m) => buildCompactedOutputRecoverySection(m === "minimal") },
|
|
92
|
+
{ id: "post-compact", includeIn: MODES_FULL_MIN, build: (p, m) => buildPostCompactionRecoverySection(p.bootstrapFiles ?? [], m === "minimal", p.postCompactionSections) },
|
|
93
|
+
{ id: "coding-fallback", includeIn: MODES_FULL_MIN, build: (p, m) => buildCodingFallbackSection(p.toolNames ?? [], m === "minimal", true) },
|
|
94
|
+
{ id: "task-delegation", includeIn: MODES_FULL_MIN, build: (p, m) => buildTaskDelegationSection(p.toolNames ?? [], m === "minimal", p.subAgentToolNames, p.mcpToolsInherited, true) },
|
|
95
|
+
// --- Operational-kept body (MODES_ALL) ---
|
|
96
|
+
{ id: "skills", includeIn: MODES_ALL, build: (p, m) => buildSkillsSection(p.skillsPrompt, m === "minimal", p.promptSkillsXml, p.activePromptSkillContent) },
|
|
97
|
+
{ id: "memory-recall", includeIn: MODES_ALL, build: (p, m) => buildMemoryRecallSection(p.hasMemoryTools ?? false, m === "minimal") },
|
|
98
|
+
{ id: "workspace", includeIn: MODES_ALL, build: (p, m) => buildWorkspaceSection(p.workspaceDir, m === "minimal") },
|
|
99
|
+
// --- Operational-stripped body ---
|
|
100
|
+
{ id: "documentation", includeIn: MODES_FULL_MIN, build: (p, m) => p.documentationConfig
|
|
101
|
+
? buildDocumentationSection(p.documentationConfig, p.toolNames ?? [], m === "minimal")
|
|
102
|
+
: [] },
|
|
103
|
+
{ id: "messaging", includeIn: MODES_ALL, build: (p, m) => buildMessagingSection(p.toolNames ?? [], m === "minimal", p.channelContext) },
|
|
104
|
+
{ id: "background", includeIn: MODES_FULL_MIN, build: (p, m) => buildBackgroundTaskSection(p.toolNames ?? [], m === "minimal", p.channelContext) },
|
|
105
|
+
{ id: "silent-replies", includeIn: MODES_FULL_MIN, build: (p, m) => buildSilentRepliesSection(m === "minimal") },
|
|
106
|
+
{ id: "heartbeats", includeIn: MODES_FULL_MIN, build: (p, m) => buildHeartbeatsSection(p.heartbeatPrompt, m === "minimal") },
|
|
107
|
+
{ id: "reactions", includeIn: MODES_FULL_MIN, build: (p, m) => buildReactionGuidanceSection(p.reactionLevel, p.channelContext?.channelType, m === "minimal") },
|
|
108
|
+
{ id: "media-sharing", includeIn: MODES_FULL_MIN, build: (p, m) => buildMediaSharingSection(p.outboundMediaEnabled, m === "minimal") },
|
|
109
|
+
{ id: "media-files", includeIn: MODES_FULL_MIN, build: (p, m) => buildMediaFilesSection(p.hasMemoryTools ?? false, (p.toolNames ?? []).includes("message"), p.workspaceDir, p.mediaPersistenceEnabled ?? false, m === "minimal") },
|
|
110
|
+
{ id: "autonomous-media", includeIn: MODES_FULL_MIN, build: (p, m) => buildAutonomousMediaSection(p.autonomousMediaEnabled ?? false, m === "minimal") },
|
|
111
|
+
{ id: "reasoning", includeIn: MODES_ALL, build: (p, m) => buildReasoningSection(p.reasoningEnabled ?? false, m === "minimal", p.reasoningTagHint ?? false) },
|
|
112
|
+
{ id: "sep", includeIn: MODES_FULL_MIN, build: (p, m) => buildTaskPlanningSection(p.sepEnabled ?? false, m === "minimal") },
|
|
113
|
+
{ id: "runtime-meta", includeIn: MODES_ALL, build: (p, m) => buildRuntimeMetadataSection(p.runtimeInfo ?? {}, m === "minimal") },
|
|
114
|
+
{ id: "sender-trust", includeIn: MODES_FULL_MIN, build: (p, m) => buildSenderTrustSection(p.senderTrustEntries ?? [], p.senderTrustDisplayMode ?? "raw", m === "minimal") },
|
|
115
|
+
{ id: "project-context", includeIn: MODES_ALL, build: (p, m) => buildProjectContextSection(p.bootstrapFiles ?? [], m === "minimal", p.excludeBootstrapFromContext ? new Set(["BOOTSTRAP.md"]) : undefined, p.workspaceProfile) },
|
|
116
|
+
];
|
|
117
|
+
/**
|
|
118
|
+
* Build all section arrays in the canonical order.
|
|
119
|
+
*
|
|
120
|
+
* Filters `SECTIONS` by mode inclusion, then builds each included descriptor.
|
|
121
|
+
* Subagent context is appended unconditionally as the last entry (matches the
|
|
122
|
+
* previous behavior).
|
|
55
123
|
*
|
|
56
124
|
* Shared by both `assembleRichSystemPrompt` and `assembleRichSystemPromptBlocks`
|
|
57
125
|
* to guarantee identity by construction between both assembly functions.
|
|
58
126
|
*
|
|
59
127
|
* @returns Array of section line arrays in fixed order
|
|
60
128
|
*/
|
|
61
|
-
function buildAllSections(params,
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// --- Semi-stable body sections (indices 4+) ---
|
|
72
|
-
buildToolingSection(params.toolNames ?? [], modelTier, params.toolSummaries), // 3
|
|
73
|
-
buildToolCallStyleSection(isMinimal, params.toolNames ?? []), // 4
|
|
74
|
-
buildSelfUpdateGatingSection(params.toolNames ?? [], isMinimal, true), // 5: deferred to tool result
|
|
75
|
-
buildConfigSecretIntegritySection(params.toolNames ?? [], isMinimal), // 5a: always-present
|
|
76
|
-
buildPrivilegedToolsSection(params.toolNames ?? [], isMinimal, true), // 5b: deferred to tool result
|
|
77
|
-
buildCompactedOutputRecoverySection(isMinimal), // 6
|
|
78
|
-
buildPostCompactionRecoverySection(// 6a
|
|
79
|
-
params.bootstrapFiles ?? [], isMinimal, params.postCompactionSections),
|
|
80
|
-
buildCodingFallbackSection(params.toolNames ?? [], isMinimal, true), // 6b: deferred to tool result
|
|
81
|
-
buildTaskDelegationSection(params.toolNames ?? [], isMinimal, params.subAgentToolNames, params.mcpToolsInherited, true), // 6c: deferred to tool result
|
|
82
|
-
buildSkillsSection(params.skillsPrompt, isMinimal, params.promptSkillsXml, params.activePromptSkillContent), // 7: Merged filesystem + prompt skills
|
|
83
|
-
buildMemoryRecallSection(params.hasMemoryTools ?? false, isMinimal), // 8
|
|
84
|
-
buildWorkspaceSection(params.workspaceDir, isMinimal), // 9
|
|
85
|
-
params.documentationConfig
|
|
86
|
-
? buildDocumentationSection(params.documentationConfig, params.toolNames ?? [], isMinimal)
|
|
87
|
-
: [], // 9b: Documentation
|
|
88
|
-
buildMessagingSection(params.toolNames ?? [], isMinimal, params.channelContext), // 10
|
|
89
|
-
buildBackgroundTaskSection(params.toolNames ?? [], isMinimal, params.channelContext), // 11
|
|
90
|
-
buildSilentRepliesSection(isMinimal), // 14
|
|
91
|
-
buildHeartbeatsSection(params.heartbeatPrompt, isMinimal), // 15
|
|
92
|
-
buildReactionGuidanceSection(params.reactionLevel, params.channelContext?.channelType, isMinimal), // 16
|
|
93
|
-
buildMediaSharingSection(params.outboundMediaEnabled, isMinimal), // 16b
|
|
94
|
-
buildMediaFilesSection(// 16c
|
|
95
|
-
params.hasMemoryTools ?? false, (params.toolNames ?? []).includes("message"), params.workspaceDir, params.mediaPersistenceEnabled ?? false, isMinimal),
|
|
96
|
-
buildAutonomousMediaSection(params.autonomousMediaEnabled ?? false, isMinimal), // 16d
|
|
97
|
-
buildReasoningSection(params.reasoningEnabled ?? false, isMinimal, params.reasoningTagHint ?? false), // 17
|
|
98
|
-
buildTaskPlanningSection(params.sepEnabled ?? false, isMinimal), // 17b: SEP task planning (static, cache-stable)
|
|
99
|
-
// buildDateTimeSection() removed from system prompt (relocated to user-message preamble in prompt-assembly.ts)
|
|
100
|
-
buildRuntimeMetadataSection(params.runtimeInfo ?? {}, isMinimal), // 19
|
|
101
|
-
// buildInboundMetadataSection() removed from system prompt (relocated to user-message preamble in prompt-assembly.ts)
|
|
102
|
-
buildSenderTrustSection(params.senderTrustEntries ?? [], params.senderTrustDisplayMode ?? "raw", isMinimal), // 20b
|
|
103
|
-
buildProjectContextSection(// 21
|
|
104
|
-
params.bootstrapFiles ?? [], isMinimal, params.excludeBootstrapFromContext ? new Set(["BOOTSTRAP.md"]) : undefined, params.workspaceProfile),
|
|
105
|
-
// Subagent: prefer structured params, fall back to raw extraSystemPrompt
|
|
106
|
-
...(params.subagentRole
|
|
107
|
-
? [buildSubagentRoleSection(params.subagentRole)]
|
|
108
|
-
: [buildSubagentContextSection(params.extraSystemPrompt)]), // 22
|
|
109
|
-
];
|
|
129
|
+
function buildAllSections(params, mode) {
|
|
130
|
+
const base = SECTIONS
|
|
131
|
+
.filter((s) => s.includeIn.has(mode))
|
|
132
|
+
.map((s) => s.build(params, mode));
|
|
133
|
+
// Subagent section: prefer structured params, fall back to raw extraSystemPrompt.
|
|
134
|
+
// Unconditional; matches the previous hand-written emission.
|
|
135
|
+
const subagent = params.subagentRole
|
|
136
|
+
? buildSubagentRoleSection(params.subagentRole)
|
|
137
|
+
: buildSubagentContextSection(params.extraSystemPrompt);
|
|
138
|
+
return [...base, subagent];
|
|
110
139
|
}
|
|
111
140
|
/**
|
|
112
141
|
* Append additional sections (backward compat for RAG memory etc.) to a joined string.
|
|
@@ -141,8 +170,7 @@ export function assembleRichSystemPrompt(params) {
|
|
|
141
170
|
if (mode === "none") {
|
|
142
171
|
return buildIdentitySection(agentName).join("\n");
|
|
143
172
|
}
|
|
144
|
-
const
|
|
145
|
-
const allSections = buildAllSections(params, isMinimal);
|
|
173
|
+
const allSections = buildAllSections(params, mode);
|
|
146
174
|
// Filter out empty arrays, join each section's lines, then join sections
|
|
147
175
|
const joined = joinSections(allSections);
|
|
148
176
|
// Append additional sections (backward compat for RAG memory etc.)
|
|
@@ -186,9 +214,13 @@ export function assembleRichSystemPromptBlocks(params) {
|
|
|
186
214
|
semiStableBody: "",
|
|
187
215
|
};
|
|
188
216
|
}
|
|
189
|
-
const
|
|
190
|
-
const allSections = buildAllSections(params, isMinimal);
|
|
217
|
+
const allSections = buildAllSections(params, mode);
|
|
191
218
|
// Split at boundaries: identity+persona | safety+language | tooling+workspace+...
|
|
219
|
+
// Boundaries are index-based on the filtered section list. In `"full"` and
|
|
220
|
+
// `"operational"` modes the first 2 entries are identity+persona (both are
|
|
221
|
+
// in MODES_FULL_OP) and the next 2 are safety+language (MODES_ALL). In
|
|
222
|
+
// `"minimal"` mode persona is dropped, so the prefix shrinks by one and the
|
|
223
|
+
// byte-identity between full/operational is NOT expected in minimal mode.
|
|
192
224
|
const staticSections = allSections.slice(0, STATIC_PREFIX_SECTION_COUNT);
|
|
193
225
|
const attributionSections = allSections.slice(STATIC_PREFIX_SECTION_COUNT, STATIC_PREFIX_SECTION_COUNT + ATTRIBUTION_SECTION_COUNT);
|
|
194
226
|
const bodySections = allSections.slice(STATIC_PREFIX_SECTION_COUNT + ATTRIBUTION_SECTION_COUNT);
|
|
@@ -37,10 +37,16 @@ export interface TruncationResult {
|
|
|
37
37
|
* System prompt verbosity mode.
|
|
38
38
|
*
|
|
39
39
|
* - `"full"`: All workspace sections included (primary agents)
|
|
40
|
+
* - `"operational"`: All workspace sections relevant to autonomous operations;
|
|
41
|
+
* drops interactive-only guidance (compaction recovery, silent replies,
|
|
42
|
+
* heartbeats, reactions, media, SEP, sender trust). Used for cron and
|
|
43
|
+
* heartbeat executions. `staticPrefix` and `attribution` cache blocks
|
|
44
|
+
* remain byte-identical to `"full"` so the cached identity/attribution
|
|
45
|
+
* prefix is shared across modes.
|
|
40
46
|
* - `"minimal"`: Only AGENTS.md + TOOLS.md (sub-agents)
|
|
41
47
|
* - `"none"`: Identity-only prompt (lightweight contexts)
|
|
42
48
|
*/
|
|
43
|
-
export type PromptMode = "full" | "minimal" | "none";
|
|
49
|
+
export type PromptMode = "full" | "operational" | "minimal" | "none";
|
|
44
50
|
/**
|
|
45
51
|
* Runtime metadata injected into the system prompt header.
|
|
46
52
|
*/
|
|
@@ -87,6 +87,18 @@ export declare function filterBootstrapFilesForSubAgent(files: BootstrapFile[]):
|
|
|
87
87
|
* @returns Filtered array containing only HEARTBEAT.md
|
|
88
88
|
*/
|
|
89
89
|
export declare function filterBootstrapFilesForLightContext(files: BootstrapFile[]): BootstrapFile[];
|
|
90
|
+
/**
|
|
91
|
+
* Filter bootstrap files for cron execution.
|
|
92
|
+
*
|
|
93
|
+
* Autonomous cron runs don't need heartbeat cadence rules, user preferences,
|
|
94
|
+
* tooling docs, or workspace bootstrap -- only the agent's persona (SOUL.md)
|
|
95
|
+
* and role behavior (ROLE.md). Mirrors the pattern of
|
|
96
|
+
* filterBootstrapFilesForLightContext.
|
|
97
|
+
*
|
|
98
|
+
* @param files - Full array of loaded bootstrap files
|
|
99
|
+
* @returns Filtered array containing only SOUL.md and ROLE.md
|
|
100
|
+
*/
|
|
101
|
+
export declare function filterBootstrapFilesForCron(files: BootstrapFile[]): BootstrapFile[];
|
|
90
102
|
/**
|
|
91
103
|
* Filter bootstrap files for group chat context.
|
|
92
104
|
*
|
|
@@ -136,6 +136,27 @@ const HEARTBEAT_LIGHT_ALLOWLIST = new Set(["HEARTBEAT.md"]);
|
|
|
136
136
|
export function filterBootstrapFilesForLightContext(files) {
|
|
137
137
|
return files.filter((f) => HEARTBEAT_LIGHT_ALLOWLIST.has(f.name));
|
|
138
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Allowlist for cron execution context.
|
|
141
|
+
* Retains ONLY SOUL.md and ROLE.md -- the persona and role behavior needed
|
|
142
|
+
* for autonomous cron runs. Strips heartbeat cadence rules, user preferences,
|
|
143
|
+
* tooling docs, and workspace bootstrap.
|
|
144
|
+
*/
|
|
145
|
+
const CRON_BOOTSTRAP_ALLOWLIST = new Set(["SOUL.md", "ROLE.md"]);
|
|
146
|
+
/**
|
|
147
|
+
* Filter bootstrap files for cron execution.
|
|
148
|
+
*
|
|
149
|
+
* Autonomous cron runs don't need heartbeat cadence rules, user preferences,
|
|
150
|
+
* tooling docs, or workspace bootstrap -- only the agent's persona (SOUL.md)
|
|
151
|
+
* and role behavior (ROLE.md). Mirrors the pattern of
|
|
152
|
+
* filterBootstrapFilesForLightContext.
|
|
153
|
+
*
|
|
154
|
+
* @param files - Full array of loaded bootstrap files
|
|
155
|
+
* @returns Filtered array containing only SOUL.md and ROLE.md
|
|
156
|
+
*/
|
|
157
|
+
export function filterBootstrapFilesForCron(files) {
|
|
158
|
+
return files.filter((f) => CRON_BOOTSTRAP_ALLOWLIST.has(f.name));
|
|
159
|
+
}
|
|
139
160
|
/**
|
|
140
161
|
* Excludelist for group chat context mode.
|
|
141
162
|
* Excludes USER.md (privacy protection: personal preferences
|
|
@@ -49,9 +49,14 @@ export function createPiEventBridge(deps) {
|
|
|
49
49
|
// -----------------------------------------------------------------
|
|
50
50
|
case "message_update": {
|
|
51
51
|
const ame = event.assistantMessageEvent;
|
|
52
|
-
if (ame && ame.type === "text_delta") {
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if (ame && (ame.type === "text_delta" || ame.type === "thinking_delta")) {
|
|
53
|
+
if (ame.type === "text_delta") {
|
|
54
|
+
// Track that visible text was produced in some turn.
|
|
55
|
+
// thinking_delta intentionally excluded — empty-final-turn detection
|
|
56
|
+
// depends on this flag reflecting user-visible text only.
|
|
57
|
+
m.textEmitted = true;
|
|
58
|
+
}
|
|
59
|
+
if (deps.onDelta && typeof ame.delta === "string") {
|
|
55
60
|
try {
|
|
56
61
|
deps.onDelta(ame.delta);
|
|
57
62
|
}
|
|
@@ -394,7 +399,7 @@ export function createPiEventBridge(deps) {
|
|
|
394
399
|
totalTokens: usage.totalTokens,
|
|
395
400
|
cost,
|
|
396
401
|
provider: deps.provider,
|
|
397
|
-
model: deps.model,
|
|
402
|
+
model: deps.getCurrentModel?.() ?? deps.model,
|
|
398
403
|
sessionKey: formatSessionKey(deps.sessionKey),
|
|
399
404
|
operationType: deps.operationType,
|
|
400
405
|
});
|
|
@@ -457,7 +462,7 @@ export function createPiEventBridge(deps) {
|
|
|
457
462
|
channelId: deps.channelId,
|
|
458
463
|
executionId: deps.executionId,
|
|
459
464
|
provider: deps.provider,
|
|
460
|
-
model: deps.model,
|
|
465
|
+
model: deps.getCurrentModel?.() ?? deps.model,
|
|
461
466
|
tokens: {
|
|
462
467
|
prompt: usage.input,
|
|
463
468
|
completion: usage.output,
|
|
@@ -475,7 +480,7 @@ export function createPiEventBridge(deps) {
|
|
|
475
480
|
cacheWriteTokens,
|
|
476
481
|
sessionKey: formatSessionKey(deps.sessionKey),
|
|
477
482
|
savedVsUncached,
|
|
478
|
-
cacheEligible: getCacheProviderInfo(deps.provider, deps.model).cacheEligible,
|
|
483
|
+
cacheEligible: getCacheProviderInfo(deps.provider, deps.getCurrentModel?.() ?? deps.model).cacheEligible,
|
|
479
484
|
responseId,
|
|
480
485
|
cacheCreation: effectiveCacheCreation,
|
|
481
486
|
});
|
|
@@ -561,17 +566,13 @@ export function createPiEventBridge(deps) {
|
|
|
561
566
|
// SEP: Extract plan from first LLM turn that has tool calls + assistant text.
|
|
562
567
|
// This runs inside the agentic loop so subsequent turns can track against the plan.
|
|
563
568
|
if (deps.executionPlan && deps.sepConfig && !deps.executionPlan.current) {
|
|
564
|
-
const hasToolCalls = Array.isArray(assistantMsg?.content) && assistantMsg.content.some((c) => {
|
|
565
|
-
const block = c;
|
|
566
|
-
return block.type === "toolCall" || block.type === "tool_use";
|
|
567
|
-
});
|
|
568
569
|
const assistantTextForPlan = Array.isArray(assistantMsg?.content)
|
|
569
570
|
? assistantMsg.content
|
|
570
571
|
.filter((c) => c?.type === "text")
|
|
571
572
|
.map((c) => c.text ?? "")
|
|
572
573
|
.join(" ")
|
|
573
574
|
: "";
|
|
574
|
-
if (
|
|
575
|
+
if (assistantTextForPlan.length > 0) {
|
|
575
576
|
const steps = extractPlanFromResponse(assistantTextForPlan, deps.sepConfig.maxSteps);
|
|
576
577
|
if (steps && steps.length >= deps.sepConfig.minSteps) {
|
|
577
578
|
const plan = {
|
|
@@ -96,6 +96,17 @@ export declare const MCP_DEFERRAL_THRESHOLD = 0.1;
|
|
|
96
96
|
* this many blocks apart cannot see each other for prefix matching.
|
|
97
97
|
* Used by: lookback window enforcement in stream-wrappers.ts. */
|
|
98
98
|
export declare const CACHE_LOOKBACK_WINDOW = 20;
|
|
99
|
+
/** Maximum message blocks before cache-aware compaction trigger.
|
|
100
|
+
* With 4 breakpoints (3 Comis + 1 SDK) and a 20-block lookback window,
|
|
101
|
+
* optimal coverage spans 4 × 20 = 80 blocks. Trigger when the count
|
|
102
|
+
* *exceeds* 60 (i.e. first fires at 61 blocks — 75% of theoretical max)
|
|
103
|
+
* to leave headroom for multi-call turns.
|
|
104
|
+
*
|
|
105
|
+
* APPROXIMATION NOTE: `messages.length` (AgentMessage[]) approximates
|
|
106
|
+
* Anthropic's request-body `messages[]` block count but is not strictly 1:1.
|
|
107
|
+
* Treat 60 as a defensive setpoint, not a calibrated threshold.
|
|
108
|
+
* Used by: llm-compaction layer cache-aware trigger. */
|
|
109
|
+
export declare const CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD = 60;
|
|
99
110
|
/** Context utilization percentage that triggers LLM compaction. Used by: llm-compaction layer. */
|
|
100
111
|
export declare const COMPACTION_TRIGGER_PERCENT = 85;
|
|
101
112
|
/** Default turns to wait before re-triggering compaction. Used by: llm-compaction layer. */
|
|
@@ -155,6 +155,17 @@ export const MCP_DEFERRAL_THRESHOLD = 0.10;
|
|
|
155
155
|
* this many blocks apart cannot see each other for prefix matching.
|
|
156
156
|
* Used by: lookback window enforcement in stream-wrappers.ts. */
|
|
157
157
|
export const CACHE_LOOKBACK_WINDOW = 20;
|
|
158
|
+
/** Maximum message blocks before cache-aware compaction trigger.
|
|
159
|
+
* With 4 breakpoints (3 Comis + 1 SDK) and a 20-block lookback window,
|
|
160
|
+
* optimal coverage spans 4 × 20 = 80 blocks. Trigger when the count
|
|
161
|
+
* *exceeds* 60 (i.e. first fires at 61 blocks — 75% of theoretical max)
|
|
162
|
+
* to leave headroom for multi-call turns.
|
|
163
|
+
*
|
|
164
|
+
* APPROXIMATION NOTE: `messages.length` (AgentMessage[]) approximates
|
|
165
|
+
* Anthropic's request-body `messages[]` block count but is not strictly 1:1.
|
|
166
|
+
* Treat 60 as a defensive setpoint, not a calibrated threshold.
|
|
167
|
+
* Used by: llm-compaction layer cache-aware trigger. */
|
|
168
|
+
export const CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD = 60;
|
|
158
169
|
// ---------------------------------------------------------------------------
|
|
159
170
|
// LLM Compaction (Layer 5: )
|
|
160
171
|
// ---------------------------------------------------------------------------
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export type { TokenBudget, TokenAnchor, ContextLayer, ContextEngine, ContextEngineDeps, ContextEngineMetrics, AssembledContext, LayerCircuitBreaker, MicrocompactionGuard, ObservationMasker, CacheOptimizationMetrics, CacheSessionStats, EvictionStats, } from "./types.js";
|
|
10
10
|
export type { ToolMaskingTier } from "./constants.js";
|
|
11
|
-
export { SAFETY_MARGIN_PERCENT, MIN_SAFETY_MARGIN_TOKENS, OUTPUT_RESERVE_TOKENS, CONTEXT_ROT_BUFFER_PERCENT, LAYER_CIRCUIT_BREAKER_THRESHOLD, DEFAULT_KEEP_WINDOW_TURNS, MAX_INLINE_TOOL_RESULT_CHARS, MAX_INLINE_MCP_TOOL_RESULT_CHARS, MAX_INLINE_FILE_READ_RESULT_CHARS, TOOL_RESULT_HARD_CAP_CHARS, DEFAULT_OBSERVATION_KEEP_WINDOW, OBSERVATION_MASKING_CHAR_THRESHOLD, TOOL_MASKING_TIERS, resolveToolMaskingTier, EPHEMERAL_TOOL_KEEP_WINDOW, CHARS_PER_TOKEN_RATIO, CHARS_PER_TOKEN_RATIO_STRUCTURED, SYSTEM_PROMPT_HASH_LENGTH, BOOTSTRAP_BUDGET_WARN_PERCENT, MIN_CACHEABLE_TOKENS, DEFAULT_MIN_CACHEABLE_TOKENS, MCP_DEFERRAL_THRESHOLD, CACHE_LOOKBACK_WINDOW, COMPACTION_TRIGGER_PERCENT, COMPACTION_COOLDOWN_TURNS, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, } from "./constants.js";
|
|
11
|
+
export { SAFETY_MARGIN_PERCENT, MIN_SAFETY_MARGIN_TOKENS, OUTPUT_RESERVE_TOKENS, CONTEXT_ROT_BUFFER_PERCENT, LAYER_CIRCUIT_BREAKER_THRESHOLD, DEFAULT_KEEP_WINDOW_TURNS, MAX_INLINE_TOOL_RESULT_CHARS, MAX_INLINE_MCP_TOOL_RESULT_CHARS, MAX_INLINE_FILE_READ_RESULT_CHARS, TOOL_RESULT_HARD_CAP_CHARS, DEFAULT_OBSERVATION_KEEP_WINDOW, OBSERVATION_MASKING_CHAR_THRESHOLD, TOOL_MASKING_TIERS, resolveToolMaskingTier, EPHEMERAL_TOOL_KEEP_WINDOW, CHARS_PER_TOKEN_RATIO, CHARS_PER_TOKEN_RATIO_STRUCTURED, SYSTEM_PROMPT_HASH_LENGTH, BOOTSTRAP_BUDGET_WARN_PERCENT, MIN_CACHEABLE_TOKENS, DEFAULT_MIN_CACHEABLE_TOKENS, MCP_DEFERRAL_THRESHOLD, CACHE_LOOKBACK_WINDOW, CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD, COMPACTION_TRIGGER_PERCENT, COMPACTION_COOLDOWN_TURNS, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, } from "./constants.js";
|
|
12
12
|
export { computeTokenBudget } from "./token-budget.js";
|
|
13
13
|
export { createContextEngine } from "./context-engine.js";
|
|
14
14
|
export { createThinkingBlockCleaner } from "./thinking-block-cleaner.js";
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
10
10
|
// Constants
|
|
11
|
-
export { SAFETY_MARGIN_PERCENT, MIN_SAFETY_MARGIN_TOKENS, OUTPUT_RESERVE_TOKENS, CONTEXT_ROT_BUFFER_PERCENT, LAYER_CIRCUIT_BREAKER_THRESHOLD, DEFAULT_KEEP_WINDOW_TURNS, MAX_INLINE_TOOL_RESULT_CHARS, MAX_INLINE_MCP_TOOL_RESULT_CHARS, MAX_INLINE_FILE_READ_RESULT_CHARS, TOOL_RESULT_HARD_CAP_CHARS, DEFAULT_OBSERVATION_KEEP_WINDOW, OBSERVATION_MASKING_CHAR_THRESHOLD, TOOL_MASKING_TIERS, resolveToolMaskingTier, EPHEMERAL_TOOL_KEEP_WINDOW, CHARS_PER_TOKEN_RATIO, CHARS_PER_TOKEN_RATIO_STRUCTURED, SYSTEM_PROMPT_HASH_LENGTH, BOOTSTRAP_BUDGET_WARN_PERCENT, MIN_CACHEABLE_TOKENS, DEFAULT_MIN_CACHEABLE_TOKENS, MCP_DEFERRAL_THRESHOLD, CACHE_LOOKBACK_WINDOW, COMPACTION_TRIGGER_PERCENT, COMPACTION_COOLDOWN_TURNS, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, } from "./constants.js";
|
|
11
|
+
export { SAFETY_MARGIN_PERCENT, MIN_SAFETY_MARGIN_TOKENS, OUTPUT_RESERVE_TOKENS, CONTEXT_ROT_BUFFER_PERCENT, LAYER_CIRCUIT_BREAKER_THRESHOLD, DEFAULT_KEEP_WINDOW_TURNS, MAX_INLINE_TOOL_RESULT_CHARS, MAX_INLINE_MCP_TOOL_RESULT_CHARS, MAX_INLINE_FILE_READ_RESULT_CHARS, TOOL_RESULT_HARD_CAP_CHARS, DEFAULT_OBSERVATION_KEEP_WINDOW, OBSERVATION_MASKING_CHAR_THRESHOLD, TOOL_MASKING_TIERS, resolveToolMaskingTier, EPHEMERAL_TOOL_KEEP_WINDOW, CHARS_PER_TOKEN_RATIO, CHARS_PER_TOKEN_RATIO_STRUCTURED, SYSTEM_PROMPT_HASH_LENGTH, BOOTSTRAP_BUDGET_WARN_PERCENT, MIN_CACHEABLE_TOKENS, DEFAULT_MIN_CACHEABLE_TOKENS, MCP_DEFERRAL_THRESHOLD, CACHE_LOOKBACK_WINDOW, CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD, COMPACTION_TRIGGER_PERCENT, COMPACTION_COOLDOWN_TURNS, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, } from "./constants.js";
|
|
12
12
|
// Token budget algebra
|
|
13
13
|
export { computeTokenBudget } from "./token-budget.js";
|
|
14
14
|
// Context engine factory
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* @module
|
|
26
26
|
*/
|
|
27
27
|
import { generateSummary } from "@mariozechner/pi-coding-agent";
|
|
28
|
-
import { COMPACTION_TRIGGER_PERCENT, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, CHARS_PER_TOKEN_RATIO, MIN_MIDDLE_MESSAGES_FOR_COMPACTION, } from "./constants.js";
|
|
28
|
+
import { COMPACTION_TRIGGER_PERCENT, COMPACTION_MAX_RETRIES, OVERSIZED_MESSAGE_CHARS_THRESHOLD, COMPACTION_REQUIRED_SECTIONS, CHARS_PER_TOKEN_RATIO, MIN_MIDDLE_MESSAGES_FOR_COMPACTION, CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD, } from "./constants.js";
|
|
29
29
|
import { estimateContextCharsWithDualRatio, estimateMessageChars, estimateWithAnchor, } from "../safety/token-estimator.js";
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
// Structured output instructions
|
|
@@ -258,27 +258,41 @@ export function createLlmCompactionLayer(config, deps) {
|
|
|
258
258
|
if (turnsSinceLastCompaction < config.compactionCooldownTurns) {
|
|
259
259
|
return messages;
|
|
260
260
|
}
|
|
261
|
-
// Step
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
if (
|
|
271
|
-
|
|
261
|
+
// Step 2b: Cache-aware block count trigger.
|
|
262
|
+
// Fires BEFORE the token-based threshold because lookback overflow
|
|
263
|
+
// causes cache breaks regardless of how few tokens the messages contain.
|
|
264
|
+
const messageCount = messages.length;
|
|
265
|
+
const blockThreshold = CACHE_AWARE_COMPACTION_BLOCK_THRESHOLD;
|
|
266
|
+
const blockCountExceeded = messageCount > blockThreshold;
|
|
267
|
+
// Step 3: Token threshold check (only when block-count trigger didn't fire)
|
|
268
|
+
let contextTokens;
|
|
269
|
+
let thresholdTokens;
|
|
270
|
+
if (!blockCountExceeded) {
|
|
271
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
272
|
+
const contextChars = estimateContextCharsWithDualRatio(messages);
|
|
273
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
274
|
+
const charBasedTokens = Math.ceil(contextChars / CHARS_PER_TOKEN_RATIO);
|
|
275
|
+
const anchor = deps.getTokenAnchor?.() ?? null;
|
|
276
|
+
contextTokens = estimateWithAnchor(anchor, messages, charBasedTokens);
|
|
277
|
+
thresholdTokens = Math.floor(budget.windowTokens * COMPACTION_TRIGGER_PERCENT / 100);
|
|
278
|
+
if (contextTokens <= thresholdTokens) {
|
|
279
|
+
return messages;
|
|
280
|
+
}
|
|
272
281
|
}
|
|
273
|
-
// Step 4:
|
|
282
|
+
// Step 4: Unified log (conditional spread keeps JSON shape clean).
|
|
274
283
|
deps.logger.warn({
|
|
275
|
-
|
|
276
|
-
|
|
284
|
+
messageCount,
|
|
285
|
+
...(blockCountExceeded
|
|
286
|
+
? { blockThreshold, trigger: "block_count" }
|
|
287
|
+
: { contextTokens, thresholdTokens, trigger: "token_threshold" }),
|
|
277
288
|
windowTokens: budget.windowTokens,
|
|
278
|
-
messageCount: messages.length,
|
|
279
289
|
errorKind: "resource",
|
|
280
|
-
hint:
|
|
281
|
-
|
|
290
|
+
hint: blockCountExceeded
|
|
291
|
+
? "Message count approaching breakpoint lookback limit; compacting to prevent cache fragmentation"
|
|
292
|
+
: "Context approaching capacity; LLM compaction will summarize older messages to free space",
|
|
293
|
+
}, blockCountExceeded
|
|
294
|
+
? "LLM compaction triggered: message count exceeds cache lookback threshold"
|
|
295
|
+
: "LLM compaction triggered: context exceeds 85% threshold");
|
|
282
296
|
// Step 5: Resolve model
|
|
283
297
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
284
298
|
let model;
|
|
@@ -60,6 +60,8 @@ export interface CacheBreakDiffPayload {
|
|
|
60
60
|
message: number;
|
|
61
61
|
sdkAuto: number;
|
|
62
62
|
};
|
|
63
|
+
/** Model ID for per-model cost attribution. Populated by pi-event-bridge.ts. */
|
|
64
|
+
model?: string;
|
|
63
65
|
}
|
|
64
66
|
export interface CacheBreakDiffWriterConfig {
|
|
65
67
|
/** Directory for diff files (e.g., ~/.comis/cache-breaks) */
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { writeFileSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
|
|
13
13
|
import { createPatch } from "diff";
|
|
14
14
|
import { safePath } from "@comis/core";
|
|
15
|
+
import { resolveModelPricing, ZERO_COST } from "../model/model-catalog.js";
|
|
15
16
|
const MAX_DIFF_FILES = 50;
|
|
16
17
|
/** DIFF-CONTENT: Maximum chars per category (system, tools) for snapshot content before diffing. */
|
|
17
18
|
const MAX_SNAPSHOT_CHARS = 50_000;
|
|
@@ -40,6 +41,7 @@ export function buildDiffableContent(system, tools, model) {
|
|
|
40
41
|
*/
|
|
41
42
|
export function createCacheBreakDiffWriter(config) {
|
|
42
43
|
let dirEnsured = false;
|
|
44
|
+
const unknownModelWarnLatch = new Set();
|
|
43
45
|
return (event) => {
|
|
44
46
|
try {
|
|
45
47
|
if (!dirEnsured) {
|
|
@@ -50,6 +52,24 @@ export function createCacheBreakDiffWriter(config) {
|
|
|
50
52
|
const ts = new Date(event.timestamp).toISOString().replace(/[:.]/g, "-");
|
|
51
53
|
const filename = `${ts}_${event.agentId}_${event.reason}.json`;
|
|
52
54
|
const filePath = safePath(config.outputDir, filename);
|
|
55
|
+
// Cost attribution: compute estimated USD impact using per-model pricing.
|
|
56
|
+
const modelId = event.model ?? "";
|
|
57
|
+
const pricing = resolveModelPricing(event.provider, modelId);
|
|
58
|
+
const pricingKnown = pricing !== ZERO_COST && pricing.input > 0;
|
|
59
|
+
const retentionDisabled = event.ttlCategory === "none";
|
|
60
|
+
const writeRatePerToken = event.ttlCategory === "long" ? pricing.cacheWrite1h : pricing.cacheWrite;
|
|
61
|
+
const readRatePerToken = pricing.cacheRead;
|
|
62
|
+
const perTokenDelta = Math.max(0, writeRatePerToken - readRatePerToken);
|
|
63
|
+
const estimatedCostUsd = retentionDisabled
|
|
64
|
+
? 0
|
|
65
|
+
: (pricingKnown ? event.tokenDrop * perTokenDelta : null);
|
|
66
|
+
if (!pricingKnown && !retentionDisabled && modelId) {
|
|
67
|
+
const latchKey = `${event.provider}:${modelId}`;
|
|
68
|
+
if (!unknownModelWarnLatch.has(latchKey)) {
|
|
69
|
+
unknownModelWarnLatch.add(latchKey);
|
|
70
|
+
config.logger.warn({ provider: event.provider, model: modelId, hint: "Cache break cost attribution unavailable for this model", errorKind: "config" }, "Unknown model pricing for cache break cost attribution");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
53
73
|
const diff = {
|
|
54
74
|
timestamp: new Date(event.timestamp).toISOString(),
|
|
55
75
|
agentId: event.agentId,
|
|
@@ -85,6 +105,22 @@ export function createCacheBreakDiffWriter(config) {
|
|
|
85
105
|
effortValue: event.effortValue,
|
|
86
106
|
// Breakpoint budget context
|
|
87
107
|
...(event.breakpointBudget && { breakpointBudget: event.breakpointBudget }),
|
|
108
|
+
// Cost attribution
|
|
109
|
+
estimatedCostUsd: estimatedCostUsd === null
|
|
110
|
+
? null
|
|
111
|
+
: Math.round(estimatedCostUsd * 100_000) / 100_000,
|
|
112
|
+
costBreakdown: {
|
|
113
|
+
writeRatePerMTok: pricingKnown
|
|
114
|
+
? Math.round(writeRatePerToken * 1_000_000 * 100) / 100
|
|
115
|
+
: null,
|
|
116
|
+
readRatePerMTok: pricingKnown
|
|
117
|
+
? Math.round(readRatePerToken * 1_000_000 * 100) / 100
|
|
118
|
+
: null,
|
|
119
|
+
tokenDrop: event.tokenDrop,
|
|
120
|
+
model: event.model ?? null,
|
|
121
|
+
ttlCategory: event.ttlCategory,
|
|
122
|
+
pricingKnown,
|
|
123
|
+
},
|
|
88
124
|
};
|
|
89
125
|
writeFileSync(filePath, JSON.stringify(diff, null, 2) + "\n");
|
|
90
126
|
// DIFF-CONTENT: Generate unified diff file alongside JSON
|
|
@@ -140,6 +140,18 @@ export interface PostExecutionParams {
|
|
|
140
140
|
* @returns true if the turn qualifies for memory storage
|
|
141
141
|
*/
|
|
142
142
|
export declare function shouldStorePairedMemory(userText: string, agentResponse: string): boolean;
|
|
143
|
+
/**
|
|
144
|
+
* Check whether paired memory content was stored recently for this agent.
|
|
145
|
+
*
|
|
146
|
+
* Exact-content hashing (not semantic similarity) -- targets the cron pattern
|
|
147
|
+
* of identical prompt + identical NO_REPLY response. Lazy eviction keeps the
|
|
148
|
+
* cache bounded without a timer.
|
|
149
|
+
*
|
|
150
|
+
* Exported for unit tests.
|
|
151
|
+
*/
|
|
152
|
+
export declare function isDuplicatePairedMemory(content: string, agentId: string): boolean;
|
|
153
|
+
/** Reset the paired-memory dedup cache. Exported for unit tests. */
|
|
154
|
+
export declare function resetPairedMemoryDedupForTests(): void;
|
|
143
155
|
/**
|
|
144
156
|
* Run post-execution cleanup for a PiExecutor turn.
|
|
145
157
|
*
|