@wrongstack/core 0.287.0 → 0.291.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-status-tracker.d.ts +13 -0
- package/dist/agent-status-tracker.d.ts.map +1 -1
- package/dist/boot.d.ts.map +1 -1
- package/dist/chronicle/context.d.ts +13 -0
- package/dist/chronicle/context.d.ts.map +1 -0
- package/dist/chronicle/decision-adapter.d.ts +13 -0
- package/dist/chronicle/decision-adapter.d.ts.map +1 -0
- package/dist/chronicle/domain-adapter.d.ts +13 -0
- package/dist/chronicle/domain-adapter.d.ts.map +1 -0
- package/dist/chronicle/file-observer.d.ts +20 -0
- package/dist/chronicle/file-observer.d.ts.map +1 -0
- package/dist/chronicle/health-monitor.d.ts +11 -0
- package/dist/chronicle/health-monitor.d.ts.map +1 -0
- package/dist/chronicle/identity.d.ts +18 -0
- package/dist/chronicle/identity.d.ts.map +1 -0
- package/dist/chronicle/index.d.ts +16 -0
- package/dist/chronicle/index.d.ts.map +1 -0
- package/dist/chronicle/index.js +2555 -0
- package/dist/chronicle/index.js.map +7 -0
- package/dist/chronicle/journal.d.ts +81 -0
- package/dist/chronicle/journal.d.ts.map +1 -0
- package/dist/chronicle/process-adapter.d.ts +14 -0
- package/dist/chronicle/process-adapter.d.ts.map +1 -0
- package/dist/chronicle/prompt-manifest.d.ts +34 -0
- package/dist/chronicle/prompt-manifest.d.ts.map +1 -0
- package/dist/chronicle/provider-adapter.d.ts +13 -0
- package/dist/chronicle/provider-adapter.d.ts.map +1 -0
- package/dist/chronicle/query.d.ts +108 -0
- package/dist/chronicle/query.d.ts.map +1 -0
- package/dist/chronicle/rollup-adapter.d.ts +14 -0
- package/dist/chronicle/rollup-adapter.d.ts.map +1 -0
- package/dist/chronicle/stream-adapter.d.ts +13 -0
- package/dist/chronicle/stream-adapter.d.ts.map +1 -0
- package/dist/chronicle/tool-adapter.d.ts +15 -0
- package/dist/chronicle/tool-adapter.d.ts.map +1 -0
- package/dist/chronicle/types.d.ts +82 -0
- package/dist/chronicle/types.d.ts.map +1 -0
- package/dist/coordination/agent-monitor.d.ts +40 -0
- package/dist/coordination/agent-monitor.d.ts.map +1 -1
- package/dist/coordination/agent-subagent-runner.d.ts +7 -1
- package/dist/coordination/agent-subagent-runner.d.ts.map +1 -1
- package/dist/coordination/agents/phase6-domain.d.ts.map +1 -1
- package/dist/coordination/autonomous-coordinator.d.ts +2 -0
- package/dist/coordination/autonomous-coordinator.d.ts.map +1 -1
- package/dist/coordination/brain-monitor.d.ts +1 -1
- package/dist/coordination/brain.d.ts +3 -2
- package/dist/coordination/brain.d.ts.map +1 -1
- package/dist/coordination/collab-debug.d.ts +0 -9
- package/dist/coordination/collab-debug.d.ts.map +1 -1
- package/dist/coordination/coordinator/error-classifier.d.ts.map +1 -1
- package/dist/coordination/dep-watcher.d.ts +2 -1
- package/dist/coordination/dep-watcher.d.ts.map +1 -1
- package/dist/coordination/director/director-collab.d.ts +1 -1
- package/dist/coordination/director/director-collab.d.ts.map +1 -1
- package/dist/coordination/director-tools.d.ts.map +1 -1
- package/dist/coordination/director.d.ts +10 -1
- package/dist/coordination/director.d.ts.map +1 -1
- package/dist/coordination/file-author-tracker.d.ts.map +1 -1
- package/dist/coordination/fleet-bus.d.ts.map +1 -1
- package/dist/coordination/fleet-event-validation.d.ts +0 -7
- package/dist/coordination/fleet-event-validation.d.ts.map +1 -1
- package/dist/coordination/fleet-spawn.d.ts.map +1 -1
- package/dist/coordination/fleet-supervisor.d.ts +1 -1
- package/dist/coordination/fleet-supervisor.d.ts.map +1 -1
- package/dist/coordination/fleet.d.ts +0 -44
- package/dist/coordination/fleet.d.ts.map +1 -1
- package/dist/coordination/global-mailbox.d.ts +7 -1
- package/dist/coordination/global-mailbox.d.ts.map +1 -1
- package/dist/coordination/index.d.ts +5 -3
- package/dist/coordination/index.d.ts.map +1 -1
- package/dist/coordination/index.js +2426 -299
- package/dist/coordination/index.js.map +4 -4
- package/dist/coordination/knowledge-graph.d.ts +2 -1
- package/dist/coordination/knowledge-graph.d.ts.map +1 -1
- package/dist/coordination/large-answer-store.d.ts +0 -6
- package/dist/coordination/large-answer-store.d.ts.map +1 -1
- package/dist/coordination/mail-tools.d.ts +2 -2
- package/dist/coordination/mail-tools.d.ts.map +1 -1
- package/dist/coordination/mailbox-hooks.d.ts +2 -0
- package/dist/coordination/mailbox-hooks.d.ts.map +1 -1
- package/dist/coordination/mailbox-http-router.d.ts +43 -0
- package/dist/coordination/mailbox-http-router.d.ts.map +1 -0
- package/dist/coordination/mailbox-message-codec.d.ts +35 -0
- package/dist/coordination/mailbox-message-codec.d.ts.map +1 -1
- package/dist/coordination/mailbox-tool.d.ts +1 -1
- package/dist/coordination/mailbox-tool.d.ts.map +1 -1
- package/dist/coordination/mailbox-types.d.ts +170 -10
- package/dist/coordination/mailbox-types.d.ts.map +1 -1
- package/dist/coordination/mailbox.d.ts +1 -1
- package/dist/coordination/mailbox.d.ts.map +1 -1
- package/dist/coordination/package-author-tracker.d.ts.map +1 -1
- package/dist/coordination/provider-status-tracker.d.ts +236 -0
- package/dist/coordination/provider-status-tracker.d.ts.map +1 -0
- package/dist/coordination/subagent-budget.d.ts +2 -1
- package/dist/coordination/subagent-budget.d.ts.map +1 -1
- package/dist/core/agent-loop.d.ts +8 -3
- package/dist/core/agent-loop.d.ts.map +1 -1
- package/dist/core/agent-response.d.ts +5 -4
- package/dist/core/agent-response.d.ts.map +1 -1
- package/dist/core/agent-tools.d.ts +1 -1
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent.d.ts +4 -2
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/context.d.ts +55 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/fallback-model.d.ts +14 -8
- package/dist/core/fallback-model.d.ts.map +1 -1
- package/dist/core/fallback-profile-manager.d.ts +118 -0
- package/dist/core/fallback-profile-manager.d.ts.map +1 -0
- package/dist/core/fleet-pulse.d.ts +2 -1
- package/dist/core/fleet-pulse.d.ts.map +1 -1
- package/dist/core/iteration-limit.d.ts +2 -1
- package/dist/core/iteration-limit.d.ts.map +1 -1
- package/dist/core/mailbox-loop.d.ts +16 -1
- package/dist/core/mailbox-loop.d.ts.map +1 -1
- package/dist/core/model-availability-calendar.d.ts +27 -0
- package/dist/core/model-availability-calendar.d.ts.map +1 -0
- package/dist/core/provider-runner.d.ts.map +1 -1
- package/dist/core/system-prompt-builder.d.ts +2 -1
- package/dist/core/system-prompt-builder.d.ts.map +1 -1
- package/dist/defaults/index.js +1920 -598
- package/dist/defaults/index.js.map +4 -4
- package/dist/execution/auto-compaction-middleware.d.ts +3 -2
- package/dist/execution/auto-compaction-middleware.d.ts.map +1 -1
- package/dist/execution/compaction-core.d.ts.map +1 -1
- package/dist/execution/council-brain.d.ts +28 -49
- package/dist/execution/council-brain.d.ts.map +1 -1
- package/dist/execution/council-orchestrator.d.ts +30 -0
- package/dist/execution/council-orchestrator.d.ts.map +1 -1
- package/dist/execution/enhance-recovery.d.ts +8 -0
- package/dist/execution/enhance-recovery.d.ts.map +1 -1
- package/dist/execution/error-handler.d.ts +2 -2
- package/dist/execution/error-handler.d.ts.map +1 -1
- package/dist/execution/eternal-autonomy.d.ts.map +1 -1
- package/dist/execution/index.js +2600 -1903
- package/dist/execution/index.js.map +4 -4
- package/dist/execution/intelligent-compactor.d.ts +0 -16
- package/dist/execution/intelligent-compactor.d.ts.map +1 -1
- package/dist/execution/model-runtime.d.ts.map +1 -1
- package/dist/execution/one-shot-llm.d.ts +9 -1
- package/dist/execution/one-shot-llm.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.d.ts +36 -0
- package/dist/execution/prompt-enhancer.d.ts.map +1 -1
- package/dist/execution/prompt-enhancer.js +158 -10
- package/dist/execution/prompt-enhancer.js.map +2 -2
- package/dist/execution/retry-policy.d.ts +0 -13
- package/dist/execution/retry-policy.d.ts.map +1 -1
- package/dist/execution/skill-loader.d.ts.map +1 -1
- package/dist/execution/tool-executor.d.ts.map +1 -1
- package/dist/goal/checkpoint.d.ts.map +1 -0
- package/dist/{autophase/auto-phase-planner.d.ts → goal/goal-planner.d.ts} +8 -8
- package/dist/goal/goal-planner.d.ts.map +1 -0
- package/dist/{autophase/auto-phase-runner.d.ts → goal/goal-runner.d.ts} +10 -10
- package/dist/goal/goal-runner.d.ts.map +1 -0
- package/dist/{autophase → goal}/index.d.ts +3 -3
- package/dist/goal/index.d.ts.map +1 -0
- package/dist/goal/phase-graph-builder.d.ts.map +1 -0
- package/dist/{autophase → goal}/phase-orchestrator.d.ts +2 -2
- package/dist/goal/phase-orchestrator.d.ts.map +1 -0
- package/dist/goal/phase-store.d.ts.map +1 -0
- package/dist/{autophase → goal}/types.d.ts +2 -2
- package/dist/goal/types.d.ts.map +1 -0
- package/dist/hq/auth-store.d.ts.map +1 -1
- package/dist/hq/commands.d.ts +2 -1
- package/dist/hq/commands.d.ts.map +1 -1
- package/dist/hq/exposure.d.ts +66 -0
- package/dist/hq/exposure.d.ts.map +1 -0
- package/dist/hq/index.d.ts +1 -0
- package/dist/hq/index.d.ts.map +1 -1
- package/dist/hq/index.js +190 -21
- package/dist/hq/index.js.map +4 -4
- package/dist/hq/mailbox-mapper.d.ts +13 -1
- package/dist/hq/mailbox-mapper.d.ts.map +1 -1
- package/dist/hq/protocol/browser.d.ts +9 -1
- package/dist/hq/protocol/browser.d.ts.map +1 -1
- package/dist/hq/protocol/client.d.ts +2 -2
- package/dist/hq/protocol/client.d.ts.map +1 -1
- package/dist/hq/protocol/core.d.ts.map +1 -1
- package/dist/hq/protocol/mailbox.d.ts +6 -0
- package/dist/hq/protocol/mailbox.d.ts.map +1 -1
- package/dist/hq/protocol/session.d.ts +4 -0
- package/dist/hq/protocol/session.d.ts.map +1 -1
- package/dist/hq/publisher.d.ts.map +1 -1
- package/dist/index.d.ts +34 -25
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30287 -22214
- package/dist/index.js.map +4 -4
- package/dist/infrastructure/index.js +60 -5
- package/dist/infrastructure/index.js.map +2 -2
- package/dist/kernel/events/agent-events.d.ts +33 -1
- package/dist/kernel/events/agent-events.d.ts.map +1 -1
- package/dist/kernel/events/file-events.d.ts +65 -0
- package/dist/kernel/events/file-events.d.ts.map +1 -0
- package/dist/kernel/events/memory-events.d.ts +36 -0
- package/dist/kernel/events/memory-events.d.ts.map +1 -1
- package/dist/kernel/events/network-events.d.ts +60 -0
- package/dist/kernel/events/network-events.d.ts.map +1 -0
- package/dist/kernel/events/process-events.d.ts +44 -0
- package/dist/kernel/events/process-events.d.ts.map +1 -0
- package/dist/kernel/events/provider-events.d.ts +89 -0
- package/dist/kernel/events/provider-events.d.ts.map +1 -1
- package/dist/kernel/events/tool-events.d.ts +10 -0
- package/dist/kernel/events/tool-events.d.ts.map +1 -1
- package/dist/kernel/events/worktree-events.d.ts +1 -1
- package/dist/kernel/events.d.ts +14 -1
- package/dist/kernel/events.d.ts.map +1 -1
- package/dist/kernel/index.js +2 -1
- package/dist/kernel/index.js.map +2 -2
- package/dist/kernel/tokens.d.ts +3 -1
- package/dist/kernel/tokens.d.ts.map +1 -1
- package/dist/mailbox-attach.d.ts.map +1 -1
- package/dist/models/index.js.map +2 -2
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +278 -0
- package/dist/observability/index.js.map +4 -4
- package/dist/observability/network-telemetry.d.ts +15 -0
- package/dist/observability/network-telemetry.d.ts.map +1 -0
- package/dist/observability/process-telemetry.d.ts +18 -0
- package/dist/observability/process-telemetry.d.ts.map +1 -0
- package/dist/observability/redact-command.d.ts +20 -0
- package/dist/observability/redact-command.d.ts.map +1 -0
- package/dist/plugins/auto-review-plugin.d.ts +56 -3
- package/dist/plugins/auto-review-plugin.d.ts.map +1 -1
- package/dist/plugins/chimera-plugin.d.ts +164 -5
- package/dist/plugins/chimera-plugin.d.ts.map +1 -1
- package/dist/plugins/review-context-builder.d.ts +52 -0
- package/dist/plugins/review-context-builder.d.ts.map +1 -0
- package/dist/security/index.d.ts +1 -0
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +186 -9
- package/dist/security/index.js.map +4 -4
- package/dist/security/kanban-boundary.d.ts +10 -0
- package/dist/security/kanban-boundary.d.ts.map +1 -0
- package/dist/security/permission-policy.d.ts +1 -0
- package/dist/security/permission-policy.d.ts.map +1 -1
- package/dist/session-registry.d.ts +80 -0
- package/dist/session-registry.d.ts.map +1 -1
- package/dist/skills/index.js.map +2 -2
- package/dist/storage/config-loader.d.ts +7 -0
- package/dist/storage/config-loader.d.ts.map +1 -1
- package/dist/storage/file-session-writer.d.ts +20 -7
- package/dist/storage/file-session-writer.d.ts.map +1 -1
- package/dist/storage/goal-coordination.d.ts +40 -0
- package/dist/storage/goal-coordination.d.ts.map +1 -0
- package/dist/storage/goal-store.d.ts +9 -5
- package/dist/storage/goal-store.d.ts.map +1 -1
- package/dist/storage/index.d.ts +4 -2
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1066 -226
- package/dist/storage/index.js.map +4 -4
- package/dist/storage/memory-consolidator.d.ts +49 -4
- package/dist/storage/memory-consolidator.d.ts.map +1 -1
- package/dist/storage/memory-graph-backend.d.ts +11 -0
- package/dist/storage/memory-graph-backend.d.ts.map +1 -1
- package/dist/storage/provider-config-watcher.d.ts +19 -0
- package/dist/storage/provider-config-watcher.d.ts.map +1 -1
- package/dist/storage/session-event-bridge.d.ts +2 -2
- package/dist/storage/session-event-bridge.d.ts.map +1 -1
- package/dist/storage/session-resume-validation.d.ts +10 -0
- package/dist/storage/session-resume-validation.d.ts.map +1 -1
- package/dist/storage/session-rewind-apply.d.ts +53 -0
- package/dist/storage/session-rewind-apply.d.ts.map +1 -0
- package/dist/storage/session-rewinder.d.ts.map +1 -1
- package/dist/storage/session-store.d.ts.map +1 -1
- package/dist/tasking/index.js.map +1 -1
- package/dist/tasking/task-store.d.ts +1 -1
- package/dist/tools/council-tool.d.ts +3 -0
- package/dist/tools/council-tool.d.ts.map +1 -1
- package/dist/tools/fallback-manage-tools.d.ts +82 -0
- package/dist/tools/fallback-manage-tools.d.ts.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +3423 -192
- package/dist/tools/index.js.map +4 -4
- package/dist/tools/one-shot-llm-tool.d.ts +2 -0
- package/dist/tools/one-shot-llm-tool.d.ts.map +1 -1
- package/dist/types/config.d.ts +76 -7
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/file-event-record.d.ts +68 -0
- package/dist/types/file-event-record.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +13 -8
- package/dist/types/index.js.map +3 -3
- package/dist/types/memory.d.ts +12 -0
- package/dist/types/memory.d.ts.map +1 -1
- package/dist/types/multi-agent.d.ts +12 -1
- package/dist/types/multi-agent.d.ts.map +1 -1
- package/dist/types/one-shot-llm.d.ts +16 -7
- package/dist/types/one-shot-llm.d.ts.map +1 -1
- package/dist/types/provider.d.ts +1 -1
- package/dist/types/provider.d.ts.map +1 -1
- package/dist/types/session.d.ts +68 -2
- package/dist/types/session.d.ts.map +1 -1
- package/dist/types/tool-executor.d.ts +2 -0
- package/dist/types/tool-executor.d.ts.map +1 -1
- package/dist/types/tool.d.ts +5 -0
- package/dist/types/tool.d.ts.map +1 -1
- package/dist/utils/atomic-write.d.ts.map +1 -1
- package/dist/utils/index.d.ts +10 -10
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +497 -254
- package/dist/utils/index.js.map +4 -4
- package/dist/utils/message-invariants.d.ts +9 -0
- package/dist/utils/message-invariants.d.ts.map +1 -1
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.d.ts.map +1 -1
- package/dist/utils/term.d.ts +279 -0
- package/dist/utils/term.d.ts.map +1 -1
- package/dist/utils/tool-wire-compact.d.ts +9 -0
- package/dist/utils/tool-wire-compact.d.ts.map +1 -1
- package/dist/utils/wstack-paths.d.ts +12 -2
- package/dist/utils/wstack-paths.d.ts.map +1 -1
- package/dist/worktree/worktree-manager.d.ts +1 -1
- package/dist/worktree/worktree-manager.d.ts.map +1 -1
- package/instructions/agents/ios.md +113 -0
- package/instructions/agents/search.md +1 -1
- package/instructions/llm/chimera-review.md +66 -9
- package/instructions/llm/memory-consolidator.md +4 -12
- package/instructions/llm/prompt-enhancer.md +3 -1
- package/instructions/sections/tool/common-patterns.md +3 -3
- package/instructions/system.md +40 -5
- package/package.json +7 -3
- package/skills/auto-review/SKILL.md +176 -0
- package/skills/mnemosyne/SKILL.md +156 -0
- package/skills/mnemosyne/instructions/agent-prompt.md +200 -0
- package/dist/autophase/auto-phase-planner.d.ts.map +0 -1
- package/dist/autophase/auto-phase-runner.d.ts.map +0 -1
- package/dist/autophase/checkpoint.d.ts.map +0 -1
- package/dist/autophase/index.d.ts.map +0 -1
- package/dist/autophase/phase-graph-builder.d.ts.map +0 -1
- package/dist/autophase/phase-orchestrator.d.ts.map +0 -1
- package/dist/autophase/phase-store.d.ts.map +0 -1
- package/dist/autophase/types.d.ts.map +0 -1
- /package/dist/{autophase → goal}/checkpoint.d.ts +0 -0
- /package/dist/{autophase → goal}/phase-graph-builder.d.ts +0 -0
- /package/dist/{autophase → goal}/phase-store.d.ts +0 -0
- /package/instructions/{autophase → goal}/phase-planner.md +0 -0
package/dist/storage/index.js
CHANGED
|
@@ -147,6 +147,12 @@ var hasStdout = () => typeof process !== "undefined" && !!process.stdout;
|
|
|
147
147
|
function isStdoutTTY() {
|
|
148
148
|
return hasStdout() && Boolean(process.stdout.isTTY);
|
|
149
149
|
}
|
|
150
|
+
var ESCAPE_TERMINATOR = Object.freeze({
|
|
151
|
+
BEL: "\x07",
|
|
152
|
+
// OSC 0 / OSC 2 (title)
|
|
153
|
+
ST: "\x1B\\"
|
|
154
|
+
// DCS / strict OSC
|
|
155
|
+
});
|
|
150
156
|
|
|
151
157
|
// src/utils/color.ts
|
|
152
158
|
var isColorTty = () => {
|
|
@@ -255,6 +261,16 @@ function expectDefined(value, label) {
|
|
|
255
261
|
}
|
|
256
262
|
|
|
257
263
|
// src/utils/message-invariants.ts
|
|
264
|
+
function hasMeaningfulContent(content) {
|
|
265
|
+
if (typeof content === "string") return content.trim().length > 0;
|
|
266
|
+
return content.some((block) => {
|
|
267
|
+
if (block.type === "text") return block.text.trim().length > 0;
|
|
268
|
+
if (block.type === "thinking") {
|
|
269
|
+
return block.thinking.trim().length > 0 || Boolean(block.signature);
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
});
|
|
273
|
+
}
|
|
258
274
|
function repairToolUseAdjacency(messages) {
|
|
259
275
|
const removedToolUses = [];
|
|
260
276
|
const removedToolResults = [];
|
|
@@ -342,8 +358,7 @@ function mapContent(msg, fn) {
|
|
|
342
358
|
return { ...msg, content: next };
|
|
343
359
|
}
|
|
344
360
|
function isEmptyMessage(msg) {
|
|
345
|
-
|
|
346
|
-
return msg.content.length === 0;
|
|
361
|
+
return !hasMeaningfulContent(msg.content);
|
|
347
362
|
}
|
|
348
363
|
|
|
349
364
|
// src/utils/regex-guard.ts
|
|
@@ -384,7 +399,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
384
399
|
|
|
385
400
|
// src/utils/safe-json.ts
|
|
386
401
|
function safeParse(input, maxBytes = 5e6) {
|
|
387
|
-
if (input
|
|
402
|
+
if (Buffer.byteLength(input, "utf8") > maxBytes) {
|
|
388
403
|
return { ok: false, error: `Input exceeds limit (${maxBytes} bytes)` };
|
|
389
404
|
}
|
|
390
405
|
try {
|
|
@@ -504,6 +519,11 @@ function resolveWstackPaths(opts) {
|
|
|
504
519
|
homeDir,
|
|
505
520
|
configDir: globalRoot,
|
|
506
521
|
globalConfig: path2.join(globalRoot, "config.json"),
|
|
522
|
+
profilesDir: path2.join(globalRoot, "profiles"),
|
|
523
|
+
profileConfig: (name) => {
|
|
524
|
+
const safe = name.replace(/[/\\:]/g, "_").replace(/\.\./g, "_");
|
|
525
|
+
return path2.join(globalRoot, "profiles", safe || "default", "config.json");
|
|
526
|
+
},
|
|
507
527
|
secretsKey: path2.join(globalRoot, ".key"),
|
|
508
528
|
globalMemory: path2.join(globalRoot, "memory.md"),
|
|
509
529
|
globalSkills: path2.join(globalRoot, "skills"),
|
|
@@ -731,7 +751,7 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
731
751
|
const dir = path3.dirname(targetPath);
|
|
732
752
|
await fs2.mkdir(dir, { recursive: true });
|
|
733
753
|
const lockPath = path3.join(dir, `.${path3.basename(targetPath)}.lock`);
|
|
734
|
-
const timeoutMs = opts.timeoutMs ??
|
|
754
|
+
const timeoutMs = opts.timeoutMs ?? 15e3;
|
|
735
755
|
const staleMs = opts.staleMs ?? 3e4;
|
|
736
756
|
const started = Date.now();
|
|
737
757
|
let handle;
|
|
@@ -741,6 +761,13 @@ async function withFileLock(targetPath, fn, opts = {}) {
|
|
|
741
761
|
await handle.writeFile(`${process.pid}:${Date.now()}`);
|
|
742
762
|
break;
|
|
743
763
|
} catch (err) {
|
|
764
|
+
if (handle) {
|
|
765
|
+
await handle.close().catch(() => {
|
|
766
|
+
});
|
|
767
|
+
await fs2.unlink(lockPath).catch(() => {
|
|
768
|
+
});
|
|
769
|
+
handle = void 0;
|
|
770
|
+
}
|
|
744
771
|
const code = err.code;
|
|
745
772
|
if (code === "ENOENT") {
|
|
746
773
|
await fs2.mkdir(dir, { recursive: true });
|
|
@@ -961,24 +988,24 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
961
988
|
/**
|
|
962
989
|
* Scrub secrets out of conversation-turn events before they are observed
|
|
963
990
|
* for the summary, written to the JSONL log, or surfaced on resume. Only
|
|
964
|
-
*
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
* also strips transient token-cache fields even when no scrubber is set.
|
|
991
|
+
* Conversation events carry free-form user/model text. Snapshot and exact
|
|
992
|
+
* message-journal normalization also strips transient token-cache fields
|
|
993
|
+
* even when no scrubber is set.
|
|
968
994
|
*/
|
|
969
995
|
scrubEvent(event) {
|
|
970
996
|
const s = this.secretScrubber;
|
|
971
|
-
|
|
997
|
+
const persistMessage = (message) => {
|
|
998
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
972
999
|
return {
|
|
973
|
-
...
|
|
974
|
-
|
|
975
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
976
|
-
return {
|
|
977
|
-
...persisted,
|
|
978
|
-
content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
|
|
979
|
-
};
|
|
980
|
-
})
|
|
1000
|
+
...persisted,
|
|
1001
|
+
content: typeof persisted.content === "string" ? s?.scrub(persisted.content) ?? persisted.content : s?.scrubObject(persisted.content) ?? persisted.content
|
|
981
1002
|
};
|
|
1003
|
+
};
|
|
1004
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
1005
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
1006
|
+
}
|
|
1007
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
1008
|
+
return { ...event, message: persistMessage(event.message) };
|
|
982
1009
|
}
|
|
983
1010
|
if (!s) return event;
|
|
984
1011
|
if (event.type === "user_input") {
|
|
@@ -1033,7 +1060,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1033
1060
|
this.bufferSynchronousEvent(event);
|
|
1034
1061
|
}
|
|
1035
1062
|
recordFileObservation(input) {
|
|
1036
|
-
if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs))
|
|
1063
|
+
if (!input.path || !/^[a-f\d]{64}$/i.test(input.hash) || !Number.isFinite(input.mtimeMs))
|
|
1064
|
+
return;
|
|
1037
1065
|
this.bufferSynchronousEvent({
|
|
1038
1066
|
type: "file_observation",
|
|
1039
1067
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1195,12 +1223,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1195
1223
|
const now = Date.now();
|
|
1196
1224
|
if (now - this.lastAppendWarnAt > 5e3) {
|
|
1197
1225
|
const suppressed = this.appendFailCount - 1;
|
|
1198
|
-
|
|
1199
|
-
console.warn(
|
|
1200
|
-
"[session] flush failed:",
|
|
1201
|
-
toErrorMessage(err),
|
|
1202
|
-
tail
|
|
1203
|
-
);
|
|
1226
|
+
console.warn(JSON.stringify({ level: "warn", event: "session.flush_failed", sessionId: this.id, message: toErrorMessage(err), ...suppressed > 0 ? { suppressed } : {}, timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
1204
1227
|
this.lastAppendWarnAt = now;
|
|
1205
1228
|
this.appendFailCount = 0;
|
|
1206
1229
|
}
|
|
@@ -1220,6 +1243,43 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1220
1243
|
});
|
|
1221
1244
|
}
|
|
1222
1245
|
}
|
|
1246
|
+
/**
|
|
1247
|
+
* Rebuild every accumulated summary counter by replaying the JSONL as it now
|
|
1248
|
+
* stands on disk.
|
|
1249
|
+
*
|
|
1250
|
+
* Only truncation needs this: the counters are fed by `observeForSummary` as
|
|
1251
|
+
* events go by and cannot un-count. Reading the whole file is fine here —
|
|
1252
|
+
* rewind is a rare, explicitly user-driven operation, and the alternative
|
|
1253
|
+
* (reversing each counter per discarded event) has to stay in lockstep with
|
|
1254
|
+
* observeForSummary forever.
|
|
1255
|
+
*
|
|
1256
|
+
* `title` is deliberately re-derived too: the first surviving user_input may
|
|
1257
|
+
* differ once earlier prompts are gone.
|
|
1258
|
+
*/
|
|
1259
|
+
async recomputeSummaryFromDisk() {
|
|
1260
|
+
if (!this.filePath) return;
|
|
1261
|
+
const raw = await fsp.readFile(this.filePath, "utf8").catch(() => null);
|
|
1262
|
+
if (raw === null) return;
|
|
1263
|
+
this.iterationCount = 0;
|
|
1264
|
+
this.toolCallCount = 0;
|
|
1265
|
+
this.toolErrorCount = 0;
|
|
1266
|
+
this.toolBreakdown = {};
|
|
1267
|
+
this.fileChangeCount = 0;
|
|
1268
|
+
this.compactionCount = 0;
|
|
1269
|
+
this.outcome = void 0;
|
|
1270
|
+
this.openToolUses = /* @__PURE__ */ new Set();
|
|
1271
|
+
this.tokenIn = 0;
|
|
1272
|
+
this.tokenOut = 0;
|
|
1273
|
+
this.summary = { ...this.summary, title: "(empty session)", tokenTotal: 0 };
|
|
1274
|
+
for (const line of raw.split("\n")) {
|
|
1275
|
+
const trimmed = line.trim();
|
|
1276
|
+
if (trimmed.length === 0) continue;
|
|
1277
|
+
try {
|
|
1278
|
+
this.observeForSummary(JSON.parse(trimmed));
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1223
1283
|
observeForSummary(event) {
|
|
1224
1284
|
if (event.type === "llm_response") {
|
|
1225
1285
|
for (const block of event.content) {
|
|
@@ -1270,10 +1330,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1270
1330
|
}
|
|
1271
1331
|
async doClose() {
|
|
1272
1332
|
if (this.pendingFileSnapshots.length > 0) {
|
|
1273
|
-
await this.writeFileSnapshot(
|
|
1274
|
-
this.activePromptIndex ?? 0,
|
|
1275
|
-
[...this.pendingFileSnapshots]
|
|
1276
|
-
);
|
|
1333
|
+
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
1277
1334
|
this.pendingFileSnapshots = [];
|
|
1278
1335
|
}
|
|
1279
1336
|
this.closed = true;
|
|
@@ -1393,7 +1450,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1393
1450
|
* so post-checkpoint content is never read or parsed — O(1) memory instead
|
|
1394
1451
|
* of O(N) JSON.parse calls over the full file.
|
|
1395
1452
|
*/
|
|
1396
|
-
async truncateToCheckpoint(targetPromptIndex) {
|
|
1453
|
+
async truncateToCheckpoint(targetPromptIndex, revertedFiles = []) {
|
|
1397
1454
|
if (!this.filePath) return 0;
|
|
1398
1455
|
if (this.flushTimer) {
|
|
1399
1456
|
clearTimeout(this.flushTimer);
|
|
@@ -1404,7 +1461,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1404
1461
|
const CHUNK_SIZE = 65536;
|
|
1405
1462
|
let fd;
|
|
1406
1463
|
let fileOffset = 0;
|
|
1407
|
-
let
|
|
1464
|
+
let pendingLine = Buffer.alloc(0);
|
|
1408
1465
|
let checkpointByteOffset = -1;
|
|
1409
1466
|
let removedCount = 0;
|
|
1410
1467
|
let targetCheckpointSeen = false;
|
|
@@ -1414,17 +1471,18 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1414
1471
|
const buf = Buffer.alloc(CHUNK_SIZE);
|
|
1415
1472
|
const { bytesRead } = await fd.read(buf, 0, CHUNK_SIZE, fileOffset);
|
|
1416
1473
|
if (bytesRead === 0) break;
|
|
1474
|
+
const carriedBytes = pendingLine.length;
|
|
1475
|
+
const chunk = carriedBytes === 0 ? buf.subarray(0, bytesRead) : Buffer.concat([pendingLine, buf.subarray(0, bytesRead)]);
|
|
1476
|
+
const chunkFileOffset = fileOffset - carriedBytes;
|
|
1417
1477
|
let chunkPos = 0;
|
|
1418
|
-
while (chunkPos <
|
|
1419
|
-
const idx =
|
|
1420
|
-
if (idx === -1)
|
|
1421
|
-
|
|
1422
|
-
break;
|
|
1423
|
-
}
|
|
1478
|
+
while (chunkPos < chunk.length) {
|
|
1479
|
+
const idx = chunk.indexOf("\n", chunkPos);
|
|
1480
|
+
if (idx === -1) break;
|
|
1481
|
+
const lineStartOffset = chunkFileOffset + chunkPos;
|
|
1424
1482
|
if (checkpointByteOffset !== -1) {
|
|
1425
1483
|
removedCount++;
|
|
1426
1484
|
} else {
|
|
1427
|
-
const lineBytes =
|
|
1485
|
+
const lineBytes = chunk.subarray(chunkPos, idx);
|
|
1428
1486
|
const line = new TextDecoder("utf-8", { fatal: false }).decode(lineBytes);
|
|
1429
1487
|
if (line.trim()) {
|
|
1430
1488
|
try {
|
|
@@ -1450,12 +1508,9 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1450
1508
|
}
|
|
1451
1509
|
}
|
|
1452
1510
|
chunkPos = idx + 1;
|
|
1453
|
-
lineStartOffset = fileOffset + chunkPos;
|
|
1454
1511
|
}
|
|
1512
|
+
pendingLine = chunk.subarray(chunkPos);
|
|
1455
1513
|
fileOffset += bytesRead;
|
|
1456
|
-
if (chunkPos >= bytesRead) {
|
|
1457
|
-
lineStartOffset = fileOffset;
|
|
1458
|
-
}
|
|
1459
1514
|
}
|
|
1460
1515
|
} finally {
|
|
1461
1516
|
await fd?.close();
|
|
@@ -1535,17 +1590,19 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1535
1590
|
this.handle = await fsp.open(this.filePath, "a", 384).catch(() => this.handle);
|
|
1536
1591
|
throw err;
|
|
1537
1592
|
}
|
|
1593
|
+
await this.recomputeSummaryFromDisk();
|
|
1594
|
+
const reverted = [...revertedFiles];
|
|
1538
1595
|
await this.append({
|
|
1539
1596
|
type: "rewound",
|
|
1540
1597
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1541
1598
|
toPromptIndex: targetPromptIndex,
|
|
1542
|
-
revertedFiles:
|
|
1599
|
+
revertedFiles: reverted
|
|
1543
1600
|
});
|
|
1544
1601
|
this.activePromptIndex = targetPromptIndex;
|
|
1545
1602
|
this.events?.emit("session.rewound", {
|
|
1546
1603
|
sessionId: this.id,
|
|
1547
1604
|
toPromptIndex: targetPromptIndex,
|
|
1548
|
-
revertedFiles:
|
|
1605
|
+
revertedFiles: reverted,
|
|
1549
1606
|
removedEvents: removedCount
|
|
1550
1607
|
});
|
|
1551
1608
|
return removedCount;
|
|
@@ -1586,7 +1643,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1586
1643
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1587
1644
|
context
|
|
1588
1645
|
});
|
|
1589
|
-
this.events?.emit("in_flight.started", {
|
|
1646
|
+
this.events?.emit("in_flight.started", {
|
|
1647
|
+
sessionId: this.id,
|
|
1648
|
+
context,
|
|
1649
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
1650
|
+
});
|
|
1590
1651
|
}
|
|
1591
1652
|
/**
|
|
1592
1653
|
* Close the in-flight marker. Idempotent in spirit
|
|
@@ -1597,10 +1658,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1597
1658
|
*/
|
|
1598
1659
|
async clearInFlightMarker(reason) {
|
|
1599
1660
|
if (this.pendingFileSnapshots.length > 0) {
|
|
1600
|
-
await this.writeFileSnapshot(
|
|
1601
|
-
this.activePromptIndex ?? 0,
|
|
1602
|
-
[...this.pendingFileSnapshots]
|
|
1603
|
-
);
|
|
1661
|
+
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
1604
1662
|
this.pendingFileSnapshots = [];
|
|
1605
1663
|
}
|
|
1606
1664
|
await this.append({
|
|
@@ -1608,7 +1666,11 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
1608
1666
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1609
1667
|
reason
|
|
1610
1668
|
});
|
|
1611
|
-
this.events?.emit("in_flight.ended", {
|
|
1669
|
+
this.events?.emit("in_flight.ended", {
|
|
1670
|
+
sessionId: this.id,
|
|
1671
|
+
reason,
|
|
1672
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
1673
|
+
});
|
|
1612
1674
|
}
|
|
1613
1675
|
};
|
|
1614
1676
|
|
|
@@ -1999,6 +2061,16 @@ function defaultRunGit(args, cwd) {
|
|
|
1999
2061
|
});
|
|
2000
2062
|
}
|
|
2001
2063
|
|
|
2064
|
+
// src/storage/session-id.ts
|
|
2065
|
+
function sanitizeModel(model) {
|
|
2066
|
+
return model.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
2067
|
+
}
|
|
2068
|
+
function generateSessionId(startedAt, _model) {
|
|
2069
|
+
const date = startedAt.slice(0, 10);
|
|
2070
|
+
const seedTime = Number.isNaN(Date.parse(startedAt)) ? Date.now() : Date.parse(startedAt);
|
|
2071
|
+
return `${date}/sess_${ulid(seedTime)}`;
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2002
2074
|
// src/storage/session-resume-validation.ts
|
|
2003
2075
|
import { createHash as createHash3 } from "node:crypto";
|
|
2004
2076
|
import * as fsp3 from "node:fs/promises";
|
|
@@ -2108,15 +2180,14 @@ function formatResumeValidationNotice(validation, projectRoot) {
|
|
|
2108
2180
|
...shown
|
|
2109
2181
|
].join("\n");
|
|
2110
2182
|
}
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
return
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
return `${date}/sess_${ulid(seedTime)}`;
|
|
2183
|
+
function formatInterruptedToolNotice(pendingToolUseCount) {
|
|
2184
|
+
if (!Number.isFinite(pendingToolUseCount) || pendingToolUseCount <= 0) return null;
|
|
2185
|
+
const plural = pendingToolUseCount === 1 ? "tool call was" : "tool calls were";
|
|
2186
|
+
return [
|
|
2187
|
+
"[SESSION RESUME INTERRUPTED WORK]",
|
|
2188
|
+
`The previous run left ${pendingToolUseCount} ${plural} in flight \u2014 no result was recorded before it stopped (crash or interrupt).`,
|
|
2189
|
+
"Those interrupted tool calls were removed from the restored conversation and were NOT re-executed. Re-run the work if it still needs doing."
|
|
2190
|
+
].join("\n");
|
|
2120
2191
|
}
|
|
2121
2192
|
|
|
2122
2193
|
// src/storage/session-summary.ts
|
|
@@ -2162,18 +2233,27 @@ function isReplayableMessage(value) {
|
|
|
2162
2233
|
const candidate = value;
|
|
2163
2234
|
return (candidate.role === "user" || candidate.role === "assistant" || candidate.role === "system") && (typeof candidate.content === "string" || Array.isArray(candidate.content));
|
|
2164
2235
|
}
|
|
2236
|
+
function trackMessageToolState(message, openToolUses) {
|
|
2237
|
+
if (!Array.isArray(message.content)) return;
|
|
2238
|
+
for (const block of message.content) {
|
|
2239
|
+
if (block.type === "tool_use") openToolUses.add(block.id);
|
|
2240
|
+
else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
function replayableMessage(value, fallbackTs) {
|
|
2244
|
+
if (!isReplayableMessage(value)) return null;
|
|
2245
|
+
const { _estTokens: _ignored, ...message } = value;
|
|
2246
|
+
return message.ts === void 0 && fallbackTs ? { ...message, ts: fallbackTs } : message;
|
|
2247
|
+
}
|
|
2165
2248
|
function applyContextSnapshot(target, openToolUses, snapshot) {
|
|
2166
2249
|
if (!Array.isArray(snapshot) || !snapshot.every(isReplayableMessage)) return false;
|
|
2167
2250
|
target.length = 0;
|
|
2168
2251
|
openToolUses.clear();
|
|
2169
2252
|
for (const raw of snapshot) {
|
|
2170
|
-
const
|
|
2253
|
+
const message = replayableMessage(raw);
|
|
2254
|
+
if (!message) return false;
|
|
2171
2255
|
target.push(message);
|
|
2172
|
-
|
|
2173
|
-
for (const block of message.content) {
|
|
2174
|
-
if (block.type === "tool_use") openToolUses.add(block.id);
|
|
2175
|
-
else if (block.type === "tool_result") openToolUses.delete(block.tool_use_id);
|
|
2176
|
-
}
|
|
2256
|
+
trackMessageToolState(message, openToolUses);
|
|
2177
2257
|
}
|
|
2178
2258
|
return true;
|
|
2179
2259
|
}
|
|
@@ -2326,10 +2406,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2326
2406
|
handle = await fsp4.open(file, "a", 384);
|
|
2327
2407
|
} catch (err) {
|
|
2328
2408
|
this.emitError(id, file, "create", toErrorMessage(err), false);
|
|
2329
|
-
throw new Error(
|
|
2330
|
-
`Failed to open session file: ${toErrorMessage(err)}`,
|
|
2331
|
-
{ cause: err }
|
|
2332
|
-
);
|
|
2409
|
+
throw new Error(`Failed to open session file: ${toErrorMessage(err)}`, { cause: err });
|
|
2333
2410
|
}
|
|
2334
2411
|
try {
|
|
2335
2412
|
const writer = new FileSessionWriter(id, handle, startedAt, meta, this.events, {
|
|
@@ -2342,10 +2419,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2342
2419
|
this.emitWrite(id, file, "create", "success", Date.now() - t0);
|
|
2343
2420
|
return writer;
|
|
2344
2421
|
} catch (err) {
|
|
2345
|
-
await handle.close().catch(
|
|
2346
|
-
"Session handle close failed",
|
|
2347
|
-
|
|
2348
|
-
|
|
2422
|
+
await handle.close().catch(
|
|
2423
|
+
(e) => this.logWarn("Session handle close failed", {
|
|
2424
|
+
event: "session_store.handle_close_failed",
|
|
2425
|
+
message: e instanceof Error ? e.message : String(e)
|
|
2426
|
+
})
|
|
2427
|
+
);
|
|
2349
2428
|
this.emitError(id, file, "create", toErrorMessage(err), true);
|
|
2350
2429
|
throw err;
|
|
2351
2430
|
}
|
|
@@ -2364,9 +2443,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2364
2443
|
}
|
|
2365
2444
|
}
|
|
2366
2445
|
if (boundary === -1) {
|
|
2367
|
-
throw new Error(
|
|
2368
|
-
`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`
|
|
2369
|
-
);
|
|
2446
|
+
throw new Error(`Checkpoint ${opts.checkpointPromptIndex} not found in session "${id}"`);
|
|
2370
2447
|
}
|
|
2371
2448
|
}
|
|
2372
2449
|
const parentPrefix = parent.events.slice(0, boundary + 1);
|
|
@@ -2413,7 +2490,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2413
2490
|
}
|
|
2414
2491
|
async materializeWorkspaceCheckpoint(checkpoint, targetRoot) {
|
|
2415
2492
|
if (!this.checkpointCas) {
|
|
2416
|
-
throw new Error(
|
|
2493
|
+
throw new Error(
|
|
2494
|
+
"Workspace checkpoint materialization requires a projectRoot-aware session store"
|
|
2495
|
+
);
|
|
2417
2496
|
}
|
|
2418
2497
|
return this.checkpointCas.materialize(checkpoint, targetRoot);
|
|
2419
2498
|
}
|
|
@@ -2421,34 +2500,44 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2421
2500
|
const file = this.sessionPath(id, ".jsonl");
|
|
2422
2501
|
const t0 = Date.now();
|
|
2423
2502
|
const data = await this.load(id);
|
|
2424
|
-
|
|
2503
|
+
const noticeMessages = [];
|
|
2504
|
+
let resumeValidation;
|
|
2425
2505
|
if (this.projectRoot) {
|
|
2426
2506
|
try {
|
|
2427
|
-
|
|
2507
|
+
resumeValidation = await validateResumeFileObservations(data.events, this.projectRoot);
|
|
2428
2508
|
const notice = formatResumeValidationNotice(resumeValidation, this.projectRoot);
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
]
|
|
2437
|
-
} : {}
|
|
2438
|
-
};
|
|
2509
|
+
if (notice) {
|
|
2510
|
+
noticeMessages.push({
|
|
2511
|
+
role: "system",
|
|
2512
|
+
content: notice,
|
|
2513
|
+
ts: resumeValidation.checkedAt
|
|
2514
|
+
});
|
|
2515
|
+
}
|
|
2439
2516
|
} catch (err) {
|
|
2440
2517
|
this.emitError(id, file, "resume_validation", toErrorMessage(err), true);
|
|
2441
2518
|
}
|
|
2442
2519
|
}
|
|
2520
|
+
const interruptedNotice = formatInterruptedToolNotice(data.pendingToolUseCount ?? 0);
|
|
2521
|
+
if (interruptedNotice) {
|
|
2522
|
+
noticeMessages.push({
|
|
2523
|
+
role: "system",
|
|
2524
|
+
content: interruptedNotice,
|
|
2525
|
+
ts: (/* @__PURE__ */ new Date()).toISOString()
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
const resumedData = {
|
|
2529
|
+
...data,
|
|
2530
|
+
...resumeValidation ? { resumeValidation } : {},
|
|
2531
|
+
...noticeMessages.length > 0 ? { messages: [...data.messages, ...noticeMessages] } : {}
|
|
2532
|
+
};
|
|
2443
2533
|
let handle;
|
|
2444
2534
|
try {
|
|
2445
2535
|
handle = await fsp4.open(file, "a", 384);
|
|
2446
2536
|
} catch (err) {
|
|
2447
2537
|
this.emitError(id, file, "resume", toErrorMessage(err), false);
|
|
2448
|
-
throw new Error(
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
);
|
|
2538
|
+
throw new Error(`Failed to open session "${id}" for append: ${toErrorMessage(err)}`, {
|
|
2539
|
+
cause: err
|
|
2540
|
+
});
|
|
2452
2541
|
}
|
|
2453
2542
|
try {
|
|
2454
2543
|
const writer = new FileSessionWriter(
|
|
@@ -2476,10 +2565,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2476
2565
|
this.emitWrite(id, file, "resume", "success", Date.now() - t0);
|
|
2477
2566
|
return { writer, data: resumedData };
|
|
2478
2567
|
} catch (err) {
|
|
2479
|
-
await handle.close().catch(
|
|
2480
|
-
"Session handle close failed",
|
|
2481
|
-
|
|
2482
|
-
|
|
2568
|
+
await handle.close().catch(
|
|
2569
|
+
(e) => this.logWarn("Session handle close failed", {
|
|
2570
|
+
event: "session_store.handle_close_failed",
|
|
2571
|
+
message: e instanceof Error ? e.message : String(e)
|
|
2572
|
+
})
|
|
2573
|
+
);
|
|
2483
2574
|
this.emitError(id, file, "resume", toErrorMessage(err), true);
|
|
2484
2575
|
throw err;
|
|
2485
2576
|
}
|
|
@@ -2530,6 +2621,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2530
2621
|
let sessionForkedEvent;
|
|
2531
2622
|
const messages = mode.full ? [] : void 0;
|
|
2532
2623
|
const openToolUses = mode.full ? /* @__PURE__ */ new Set() : void 0;
|
|
2624
|
+
let exactJournalActive = false;
|
|
2533
2625
|
let usage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
2534
2626
|
const stream = createReadStream(file, { encoding: "utf8" });
|
|
2535
2627
|
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
@@ -2554,20 +2646,59 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2554
2646
|
sessionForkedEvent = ev;
|
|
2555
2647
|
}
|
|
2556
2648
|
if (mode.full && messages !== void 0 && openToolUses !== void 0) {
|
|
2557
|
-
if (ev.type === "
|
|
2649
|
+
if (ev.type === "message_appended" && ev.version === 1) {
|
|
2650
|
+
const message = replayableMessage(ev.message, ev.ts);
|
|
2651
|
+
if (message) {
|
|
2652
|
+
if (!exactJournalActive) {
|
|
2653
|
+
messages.length = 0;
|
|
2654
|
+
openToolUses.clear();
|
|
2655
|
+
exactJournalActive = true;
|
|
2656
|
+
}
|
|
2657
|
+
messages.push(message);
|
|
2658
|
+
trackMessageToolState(message, openToolUses);
|
|
2659
|
+
} else {
|
|
2660
|
+
this.events?.emit("session.damaged", {
|
|
2661
|
+
sessionId: id,
|
|
2662
|
+
detail: "Ignored malformed message_appended event"
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
} else if (ev.type === "message_updated" && ev.version === 1) {
|
|
2666
|
+
const message = replayableMessage(ev.message, ev.ts);
|
|
2667
|
+
if (message && exactJournalActive && ev.index >= 0 && ev.index < messages.length) {
|
|
2668
|
+
messages[ev.index] = message;
|
|
2669
|
+
openToolUses.clear();
|
|
2670
|
+
for (const current of messages) trackMessageToolState(current, openToolUses);
|
|
2671
|
+
} else {
|
|
2672
|
+
this.events?.emit("session.damaged", {
|
|
2673
|
+
sessionId: id,
|
|
2674
|
+
detail: `Ignored malformed message_updated event at index ${ev.index}`
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
} else if (ev.type === "messages_replaced" && ev.version === 1) {
|
|
2678
|
+
if (applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
2679
|
+
exactJournalActive = true;
|
|
2680
|
+
} else {
|
|
2681
|
+
this.events?.emit("session.damaged", {
|
|
2682
|
+
sessionId: id,
|
|
2683
|
+
detail: "Ignored malformed messages_replaced event"
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2686
|
+
} else if (ev.type === "context_snapshot") {
|
|
2558
2687
|
if (!applyContextSnapshot(messages, openToolUses, ev.messages)) {
|
|
2559
2688
|
this.events?.emit("session.damaged", {
|
|
2560
2689
|
sessionId: id,
|
|
2561
2690
|
detail: "Ignored malformed context_snapshot event"
|
|
2562
2691
|
});
|
|
2563
2692
|
}
|
|
2564
|
-
} else if (ev.type === "user_input") {
|
|
2693
|
+
} else if (!exactJournalActive && ev.type === "user_input") {
|
|
2565
2694
|
openToolUses.clear();
|
|
2566
2695
|
messages.push({ role: "user", content: ev.content, ts: ev.ts });
|
|
2567
2696
|
} else if (ev.type === "llm_response") {
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2697
|
+
if (!exactJournalActive) {
|
|
2698
|
+
messages.push({ role: "assistant", content: ev.content, ts: ev.ts });
|
|
2699
|
+
for (const b of ev.content) {
|
|
2700
|
+
if (b.type === "tool_use") openToolUses.add(b.id);
|
|
2701
|
+
}
|
|
2571
2702
|
}
|
|
2572
2703
|
usage = {
|
|
2573
2704
|
input: usage.input + (ev.usage.input ?? 0),
|
|
@@ -2575,7 +2706,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2575
2706
|
cacheRead: (usage.cacheRead ?? 0) + (ev.usage.cacheRead ?? 0),
|
|
2576
2707
|
cacheWrite: (usage.cacheWrite ?? 0) + (ev.usage.cacheWrite ?? 0)
|
|
2577
2708
|
};
|
|
2578
|
-
} else if (ev.type === "tool_result") {
|
|
2709
|
+
} else if (!exactJournalActive && ev.type === "tool_result") {
|
|
2579
2710
|
if (!openToolUses.has(ev.id)) {
|
|
2580
2711
|
this.events?.emit("session.damaged", {
|
|
2581
2712
|
sessionId: id,
|
|
@@ -2647,7 +2778,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2647
2778
|
} : void 0
|
|
2648
2779
|
};
|
|
2649
2780
|
const toolCallEnds = extractToolCallEnds(events);
|
|
2650
|
-
const
|
|
2781
|
+
const pendingToolUseCount = openToolUses && openToolUses.size > 0 ? openToolUses.size : void 0;
|
|
2782
|
+
const data = {
|
|
2783
|
+
metadata: meta,
|
|
2784
|
+
events,
|
|
2785
|
+
messages: finalMessages,
|
|
2786
|
+
usage,
|
|
2787
|
+
toolCallEnds,
|
|
2788
|
+
...pendingToolUseCount !== void 0 ? { pendingToolUseCount } : {}
|
|
2789
|
+
};
|
|
2651
2790
|
if (mode.full) {
|
|
2652
2791
|
if (this._loadCache.size >= _DefaultSessionStore.LOAD_CACHE_MAX_ENTRIES) {
|
|
2653
2792
|
const oldest = this._loadCache.keys().next().value;
|
|
@@ -2663,7 +2802,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2663
2802
|
errorMsg = toErrorMessage(err);
|
|
2664
2803
|
throw err;
|
|
2665
2804
|
} finally {
|
|
2666
|
-
this.emitRead(
|
|
2805
|
+
this.emitRead(
|
|
2806
|
+
id,
|
|
2807
|
+
file,
|
|
2808
|
+
mode.full ? "load" : "load_events_only",
|
|
2809
|
+
outcome,
|
|
2810
|
+
Date.now() - t0,
|
|
2811
|
+
errorMsg
|
|
2812
|
+
);
|
|
2667
2813
|
if (cacheHit) {
|
|
2668
2814
|
this.events?.emit("storage.cache_hit", {
|
|
2669
2815
|
sessionId: id,
|
|
@@ -2865,7 +3011,15 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2865
3011
|
outcome = "failure";
|
|
2866
3012
|
errorMsg = toErrorMessage(err);
|
|
2867
3013
|
} finally {
|
|
2868
|
-
this.emitWrite(
|
|
3014
|
+
this.emitWrite(
|
|
3015
|
+
"~compact~",
|
|
3016
|
+
this.indexFile,
|
|
3017
|
+
"compact",
|
|
3018
|
+
outcome,
|
|
3019
|
+
Date.now() - t0,
|
|
3020
|
+
void 0,
|
|
3021
|
+
errorMsg
|
|
3022
|
+
);
|
|
2869
3023
|
}
|
|
2870
3024
|
}
|
|
2871
3025
|
/**
|
|
@@ -2937,7 +3091,9 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2937
3091
|
*/
|
|
2938
3092
|
async rebuildIndex() {
|
|
2939
3093
|
const ids = await this.collectSessionIds(this.dir);
|
|
2940
|
-
const summaries = await Promise.all(
|
|
3094
|
+
const summaries = await Promise.all(
|
|
3095
|
+
ids.map((id) => this.summaryFor(id).catch(() => null))
|
|
3096
|
+
);
|
|
2941
3097
|
const valid = summaries.filter((s) => s !== null);
|
|
2942
3098
|
const lines = valid.map((s) => JSON.stringify(s)).join("\n") + "\n";
|
|
2943
3099
|
await withFileLock(this.indexFile, async () => {
|
|
@@ -2978,7 +3134,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
2978
3134
|
const shardKeys = [""];
|
|
2979
3135
|
for (const entry of entries) {
|
|
2980
3136
|
if (entry.name.startsWith(".") && entry.name !== ".wrongstack") continue;
|
|
2981
|
-
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
3137
|
+
if (entry.name === "shared" || entry.name === "subagents" || entry.name === "attachments")
|
|
3138
|
+
continue;
|
|
2982
3139
|
if (entry.isDirectory()) shardKeys.push(entry.name);
|
|
2983
3140
|
}
|
|
2984
3141
|
return shardKeys;
|
|
@@ -3100,10 +3257,11 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3100
3257
|
await atomicWrite(manifest, JSON.stringify(summary), { mode: 384 }).catch((err) => {
|
|
3101
3258
|
const msg = toErrorMessage(err);
|
|
3102
3259
|
this.emitError(id, manifest, "summary_fallback", msg, true);
|
|
3103
|
-
this.logWarn(
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
3260
|
+
this.logWarn("Session manifest write failed", {
|
|
3261
|
+
event: "session_store.manifest_write_failed",
|
|
3262
|
+
sessionId: id,
|
|
3263
|
+
message: msg
|
|
3264
|
+
});
|
|
3107
3265
|
});
|
|
3108
3266
|
outcome = "failure";
|
|
3109
3267
|
errorMsg = "summary fallback \xE2\u20AC\u201D manifest rebuilt";
|
|
@@ -3210,24 +3368,28 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3210
3368
|
if (r.status === "rejected") {
|
|
3211
3369
|
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
3212
3370
|
if (r.reason?.code !== "ENOENT") {
|
|
3213
|
-
console.warn(
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3371
|
+
console.warn(
|
|
3372
|
+
JSON.stringify({
|
|
3373
|
+
level: "warn",
|
|
3374
|
+
event: "session_store.delete_failed",
|
|
3375
|
+
sessionId: id,
|
|
3376
|
+
message: msg,
|
|
3377
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3378
|
+
})
|
|
3379
|
+
);
|
|
3220
3380
|
}
|
|
3221
3381
|
}
|
|
3222
3382
|
}
|
|
3223
3383
|
await fsp4.rm(sessDir, { recursive: true, force: true }).catch((err) => {
|
|
3224
|
-
console.warn(
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3384
|
+
console.warn(
|
|
3385
|
+
JSON.stringify({
|
|
3386
|
+
level: "warn",
|
|
3387
|
+
event: "session_store.rmdir_failed",
|
|
3388
|
+
sessionId: id,
|
|
3389
|
+
message: toErrorMessage(err),
|
|
3390
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
3391
|
+
})
|
|
3392
|
+
);
|
|
3231
3393
|
});
|
|
3232
3394
|
await this.writeTombstone(id);
|
|
3233
3395
|
}
|
|
@@ -4516,6 +4678,43 @@ function isDirectory(candidate) {
|
|
|
4516
4678
|
}
|
|
4517
4679
|
|
|
4518
4680
|
// src/storage/memory-consolidator.ts
|
|
4681
|
+
function isSuperMemoryRecord(value) {
|
|
4682
|
+
return typeof value === "object" && value !== null && typeof value.text === "string";
|
|
4683
|
+
}
|
|
4684
|
+
function toPromptEntries(records, limit) {
|
|
4685
|
+
return records.filter(isSuperMemoryRecord).filter((record) => record.scope === "project").slice(0, limit).map((record) => ({
|
|
4686
|
+
scope: "project-memory",
|
|
4687
|
+
text: record.text,
|
|
4688
|
+
ts: record.updatedAt ?? record.createdAt ?? ""
|
|
4689
|
+
}));
|
|
4690
|
+
}
|
|
4691
|
+
function toSuperMemoryKind(type) {
|
|
4692
|
+
switch (type) {
|
|
4693
|
+
case "decision":
|
|
4694
|
+
case "convention":
|
|
4695
|
+
case "preference":
|
|
4696
|
+
case "anti_pattern":
|
|
4697
|
+
return type;
|
|
4698
|
+
case "reference":
|
|
4699
|
+
return "file_note";
|
|
4700
|
+
default:
|
|
4701
|
+
return "fact";
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
function importanceFromPriority(priority) {
|
|
4705
|
+
switch (priority) {
|
|
4706
|
+
case "critical":
|
|
4707
|
+
return 0.95;
|
|
4708
|
+
case "high":
|
|
4709
|
+
return 0.8;
|
|
4710
|
+
case "medium":
|
|
4711
|
+
return 0.55;
|
|
4712
|
+
case "low":
|
|
4713
|
+
return 0.25;
|
|
4714
|
+
default:
|
|
4715
|
+
return 0.6;
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4519
4718
|
function buildConsolidationPrompt(finalText, iterations, existingEntries) {
|
|
4520
4719
|
const existingBlock = existingEntries.length > 0 ? `
|
|
4521
4720
|
|
|
@@ -4531,6 +4730,7 @@ var SessionMemoryConsolidator = class {
|
|
|
4531
4730
|
name = "session-memory-consolidator";
|
|
4532
4731
|
owner = "core";
|
|
4533
4732
|
memoryStore;
|
|
4733
|
+
superMemory;
|
|
4534
4734
|
provider;
|
|
4535
4735
|
model;
|
|
4536
4736
|
minIterations;
|
|
@@ -4538,6 +4738,7 @@ var SessionMemoryConsolidator = class {
|
|
|
4538
4738
|
oneShotOrchestrator;
|
|
4539
4739
|
constructor(opts) {
|
|
4540
4740
|
this.memoryStore = opts.memoryStore;
|
|
4741
|
+
this.superMemory = opts.superMemory;
|
|
4541
4742
|
this.provider = opts.provider;
|
|
4542
4743
|
this.model = opts.model;
|
|
4543
4744
|
this.minIterations = opts.minIterations ?? 2;
|
|
@@ -4553,9 +4754,20 @@ var SessionMemoryConsolidator = class {
|
|
|
4553
4754
|
const _finalText = result.finalText;
|
|
4554
4755
|
const _iterations = result.iterations;
|
|
4555
4756
|
const _model = this.model ?? ctx.model;
|
|
4757
|
+
const _sessionId = ctx.session.id;
|
|
4556
4758
|
void (async () => {
|
|
4557
4759
|
try {
|
|
4558
|
-
|
|
4760
|
+
let existingEntries;
|
|
4761
|
+
if (this.superMemory) {
|
|
4762
|
+
const records = this.superMemory.listSuper ? await this.superMemory.listSuper(["active"]) : await this.superMemory.searchSuper?.("", {
|
|
4763
|
+
limit: this.maxExistingEntries,
|
|
4764
|
+
scope: "project",
|
|
4765
|
+
includeStatuses: ["active"]
|
|
4766
|
+
}) ?? [];
|
|
4767
|
+
existingEntries = toPromptEntries(records, this.maxExistingEntries);
|
|
4768
|
+
} else {
|
|
4769
|
+
existingEntries = await this.memoryStore.list("project-memory", this.maxExistingEntries);
|
|
4770
|
+
}
|
|
4559
4771
|
const prompt = buildConsolidationPrompt(
|
|
4560
4772
|
_finalText,
|
|
4561
4773
|
_iterations,
|
|
@@ -4568,8 +4780,7 @@ var SessionMemoryConsolidator = class {
|
|
|
4568
4780
|
userPrompt: "Review the session and return memory operations as JSON.",
|
|
4569
4781
|
model: _model ?? "deepseek-chat",
|
|
4570
4782
|
maxTokens: 500,
|
|
4571
|
-
timeoutMs: 15e3
|
|
4572
|
-
fallbackProfile: "memory"
|
|
4783
|
+
timeoutMs: 15e3
|
|
4573
4784
|
});
|
|
4574
4785
|
text = oneShotResult.text;
|
|
4575
4786
|
} else {
|
|
@@ -4593,48 +4804,33 @@ var SessionMemoryConsolidator = class {
|
|
|
4593
4804
|
const parsed = JSON.parse(jsonMatch[0]);
|
|
4594
4805
|
if (!Array.isArray(parsed.operations) || parsed.operations.length === 0) return;
|
|
4595
4806
|
let added = 0;
|
|
4596
|
-
let edited = 0;
|
|
4597
|
-
let deleted = 0;
|
|
4598
4807
|
for (const op of parsed.operations) {
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
});
|
|
4619
|
-
edited++;
|
|
4620
|
-
}
|
|
4621
|
-
break;
|
|
4622
|
-
}
|
|
4623
|
-
case "delete": {
|
|
4624
|
-
if (op.query) {
|
|
4625
|
-
const n = await this.memoryStore.forget(op.query);
|
|
4626
|
-
deleted += n;
|
|
4627
|
-
}
|
|
4628
|
-
break;
|
|
4808
|
+
try {
|
|
4809
|
+
if (!op || typeof op !== "object" || op.action !== "add") continue;
|
|
4810
|
+
if (typeof op.text !== "string" || !op.text.trim()) continue;
|
|
4811
|
+
const memoryText = op.text.trim();
|
|
4812
|
+
if (this.superMemory) {
|
|
4813
|
+
await this.superMemory.rememberSuper({
|
|
4814
|
+
text: memoryText,
|
|
4815
|
+
scope: "project",
|
|
4816
|
+
kind: toSuperMemoryKind(op.type),
|
|
4817
|
+
tags: op.tags,
|
|
4818
|
+
importance: importanceFromPriority(op.priority),
|
|
4819
|
+
sources: [{ type: "session", sessionId: _sessionId }]
|
|
4820
|
+
});
|
|
4821
|
+
} else {
|
|
4822
|
+
await this.memoryStore.remember(memoryText, void 0, {
|
|
4823
|
+
type: op.type,
|
|
4824
|
+
tags: op.tags,
|
|
4825
|
+
priority: op.priority
|
|
4826
|
+
});
|
|
4629
4827
|
}
|
|
4828
|
+
added++;
|
|
4829
|
+
} catch {
|
|
4630
4830
|
}
|
|
4631
4831
|
}
|
|
4632
|
-
if (added > 0
|
|
4633
|
-
|
|
4634
|
-
if (added) parts.push(`${added} added`);
|
|
4635
|
-
if (edited) parts.push(`${edited} edited`);
|
|
4636
|
-
if (deleted) parts.push(`${deleted} deleted`);
|
|
4637
|
-
process.stderr.write(`[memory] Session consolidation: ${parts.join(", ")}
|
|
4832
|
+
if (added > 0) {
|
|
4833
|
+
process.stderr.write(`[memory] Session consolidation: ${added} added
|
|
4638
4834
|
`);
|
|
4639
4835
|
}
|
|
4640
4836
|
} catch {
|
|
@@ -4785,6 +4981,15 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
4785
4981
|
if (typeof decrypted.baseUrl === "string") snapshot.baseUrl = decrypted.baseUrl;
|
|
4786
4982
|
if (typeof decrypted.uiLocale === "string" && decrypted.uiLocale)
|
|
4787
4983
|
snapshot.uiLocale = decrypted.uiLocale;
|
|
4984
|
+
if (Array.isArray(decrypted.fallbackModels)) snapshot.fallbackModels = decrypted.fallbackModels;
|
|
4985
|
+
if (decrypted.fallbackProfiles) snapshot.fallbackProfiles = decrypted.fallbackProfiles;
|
|
4986
|
+
if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
|
|
4987
|
+
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
4988
|
+
snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
|
|
4989
|
+
if (decrypted.modelMatrix) snapshot.modelMatrix = decrypted.modelMatrix;
|
|
4990
|
+
if (typeof decrypted.fallbackAuto === "boolean") snapshot.fallbackAuto = decrypted.fallbackAuto;
|
|
4991
|
+
if (Array.isArray(decrypted.modelAvailabilitySchedule))
|
|
4992
|
+
snapshot.modelAvailabilitySchedule = decrypted.modelAvailabilitySchedule;
|
|
4788
4993
|
return snapshot;
|
|
4789
4994
|
}
|
|
4790
4995
|
function serializeSnapshot(s) {
|
|
@@ -4792,7 +4997,14 @@ function serializeSnapshot(s) {
|
|
|
4792
4997
|
providers: s.providers,
|
|
4793
4998
|
apiKey: s.apiKey ?? null,
|
|
4794
4999
|
baseUrl: s.baseUrl ?? null,
|
|
4795
|
-
uiLocale: s.uiLocale ?? null
|
|
5000
|
+
uiLocale: s.uiLocale ?? null,
|
|
5001
|
+
fallbackModels: s.fallbackModels ?? null,
|
|
5002
|
+
fallbackProfiles: s.fallbackProfiles ?? null,
|
|
5003
|
+
favoriteModels: s.favoriteModels ?? null,
|
|
5004
|
+
favoriteModelsOnly: s.favoriteModelsOnly ?? null,
|
|
5005
|
+
modelMatrix: s.modelMatrix ?? null,
|
|
5006
|
+
fallbackAuto: s.fallbackAuto ?? null,
|
|
5007
|
+
modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
|
|
4796
5008
|
});
|
|
4797
5009
|
}
|
|
4798
5010
|
function watchProviderConfig(configPath, vault, onChange, opts = {}) {
|
|
@@ -5153,6 +5365,7 @@ var defaultIndexing = {
|
|
|
5153
5365
|
};
|
|
5154
5366
|
var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
5155
5367
|
"version",
|
|
5368
|
+
"activeProfile",
|
|
5156
5369
|
"model",
|
|
5157
5370
|
"cwd",
|
|
5158
5371
|
"context",
|
|
@@ -5175,6 +5388,7 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
5175
5388
|
"fallbackProfiles",
|
|
5176
5389
|
"favoriteModels",
|
|
5177
5390
|
"favoriteModelsOnly",
|
|
5391
|
+
"modelAvailabilitySchedule",
|
|
5178
5392
|
"fallbackAuto",
|
|
5179
5393
|
"models",
|
|
5180
5394
|
"modelMatrix",
|
|
@@ -5210,6 +5424,7 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
5210
5424
|
];
|
|
5211
5425
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
5212
5426
|
"version",
|
|
5427
|
+
"activeProfile",
|
|
5213
5428
|
"provider",
|
|
5214
5429
|
"model",
|
|
5215
5430
|
"apiKey",
|
|
@@ -5226,6 +5441,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
5226
5441
|
"fallbackProfiles",
|
|
5227
5442
|
"favoriteModels",
|
|
5228
5443
|
"favoriteModelsOnly",
|
|
5444
|
+
"modelAvailabilitySchedule",
|
|
5229
5445
|
"fallbackAuto",
|
|
5230
5446
|
"hooks",
|
|
5231
5447
|
"plugins",
|
|
@@ -5430,12 +5646,17 @@ var DefaultConfigLoader = class {
|
|
|
5430
5646
|
let cfg = { ...BEHAVIOR_DEFAULTS };
|
|
5431
5647
|
await this.ensureGlobalDefaults();
|
|
5432
5648
|
const inProjectCollides = samePath(this.paths.inProjectConfig, this.paths.globalConfig) || samePath(this.paths.inProjectConfig, this.paths.projectLocalConfig);
|
|
5433
|
-
const [
|
|
5649
|
+
const [bootstrap, local, inProject] = await Promise.all([
|
|
5434
5650
|
this.readJson(this.paths.globalConfig),
|
|
5435
5651
|
this.readJson(this.paths.projectLocalConfig),
|
|
5436
5652
|
inProjectCollides ? Promise.resolve({}) : this.readJson(this.paths.inProjectConfig)
|
|
5437
5653
|
]);
|
|
5438
|
-
cfg = deepMerge2(cfg,
|
|
5654
|
+
cfg = deepMerge2(cfg, bootstrap);
|
|
5655
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
5656
|
+
const profileCfg = await this.readJson(this.paths.profileConfig(profileName));
|
|
5657
|
+
if (Object.keys(profileCfg).length > 0) {
|
|
5658
|
+
cfg = deepMerge2(cfg, profileCfg);
|
|
5659
|
+
}
|
|
5439
5660
|
cfg = deepMerge2(cfg, local);
|
|
5440
5661
|
cfg = deepMerge2(
|
|
5441
5662
|
cfg,
|
|
@@ -5461,10 +5682,11 @@ var DefaultConfigLoader = class {
|
|
|
5461
5682
|
cfg = deepMerge2(cfg, patch);
|
|
5462
5683
|
}
|
|
5463
5684
|
} catch (err) {
|
|
5464
|
-
this.logWarn(
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
5685
|
+
this.logWarn("Config source load failed", {
|
|
5686
|
+
event: "config.source_load_failed",
|
|
5687
|
+
source: src.name,
|
|
5688
|
+
message: toErrorMessage(err)
|
|
5689
|
+
});
|
|
5468
5690
|
}
|
|
5469
5691
|
}
|
|
5470
5692
|
if (opts.cliFlags) {
|
|
@@ -5503,6 +5725,7 @@ var DefaultConfigLoader = class {
|
|
|
5503
5725
|
try {
|
|
5504
5726
|
await withFileLock(fp, async () => {
|
|
5505
5727
|
let parsed;
|
|
5728
|
+
let fileExisted = true;
|
|
5506
5729
|
try {
|
|
5507
5730
|
const raw = await fs6.readFile(fp, "utf8");
|
|
5508
5731
|
const result = safeParse(raw);
|
|
@@ -5523,25 +5746,115 @@ var DefaultConfigLoader = class {
|
|
|
5523
5746
|
durationMs: Date.now() - t0,
|
|
5524
5747
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5525
5748
|
});
|
|
5526
|
-
this.logWarn(
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5749
|
+
this.logWarn("Config defaults read failed", {
|
|
5750
|
+
event: "config.defaults_read_failed",
|
|
5751
|
+
path: fp,
|
|
5752
|
+
message: toErrorMessage(err)
|
|
5753
|
+
});
|
|
5530
5754
|
return;
|
|
5531
5755
|
}
|
|
5756
|
+
fileExisted = false;
|
|
5532
5757
|
parsed = {};
|
|
5533
5758
|
}
|
|
5534
|
-
const
|
|
5535
|
-
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
|
|
5539
|
-
|
|
5759
|
+
const bootstrap = {
|
|
5760
|
+
version: 1,
|
|
5761
|
+
activeProfile: parsed.activeProfile ?? "default"
|
|
5762
|
+
};
|
|
5763
|
+
let needsBootstrapWrite = false;
|
|
5764
|
+
if (parsed.version !== 1) {
|
|
5765
|
+
needsBootstrapWrite = true;
|
|
5766
|
+
}
|
|
5767
|
+
if (parsed.activeProfile === void 0) {
|
|
5768
|
+
needsBootstrapWrite = true;
|
|
5769
|
+
}
|
|
5770
|
+
if (needsBootstrapWrite) {
|
|
5771
|
+
await atomicWrite(fp, JSON.stringify(bootstrap, null, 2), { mode: 384 });
|
|
5772
|
+
this.events?.emit("storage.write", {
|
|
5773
|
+
sessionId: "~config~",
|
|
5774
|
+
store: "config",
|
|
5775
|
+
filePath: fp,
|
|
5776
|
+
operation: "ensure_defaults",
|
|
5777
|
+
outcome: "success",
|
|
5778
|
+
durationMs: Date.now() - t0,
|
|
5779
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5782
|
+
const profileName = bootstrap.activeProfile ?? "default";
|
|
5783
|
+
const profileFp = this.paths.profileConfig(profileName);
|
|
5784
|
+
await this.ensureProfileConfig(profileFp, parsed, fileExisted);
|
|
5785
|
+
});
|
|
5786
|
+
} catch (err) {
|
|
5787
|
+
this.events?.emit("storage.error", {
|
|
5788
|
+
sessionId: "~config~",
|
|
5789
|
+
store: "config",
|
|
5790
|
+
filePath: fp,
|
|
5791
|
+
operation: "ensure_defaults",
|
|
5792
|
+
outcome: "failure",
|
|
5793
|
+
error: storageErrorString(err),
|
|
5794
|
+
recoverable: false,
|
|
5795
|
+
durationMs: Date.now() - t0,
|
|
5796
|
+
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5797
|
+
});
|
|
5798
|
+
this.logWarn("Config defaults write failed", {
|
|
5799
|
+
event: "config.defaults_write_failed",
|
|
5800
|
+
path: fp,
|
|
5801
|
+
message: toErrorMessage(err)
|
|
5802
|
+
});
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
/**
|
|
5806
|
+
* Ensure a profile config file exists at the given path.
|
|
5807
|
+
* On first boot: migrate content from the old flat global config, or seed
|
|
5808
|
+
* with behavior defaults. Subsequent boots: fill any missing keys from
|
|
5809
|
+
* BEHAVIOR_DEFAULTS (keeping user settings intact).
|
|
5810
|
+
*/
|
|
5811
|
+
async ensureProfileConfig(profileFp, oldGlobalParsed, oldFileExisted) {
|
|
5812
|
+
const t0 = Date.now();
|
|
5813
|
+
try {
|
|
5814
|
+
await withFileLock(profileFp, async () => {
|
|
5815
|
+
let parsed;
|
|
5816
|
+
let existed = true;
|
|
5817
|
+
try {
|
|
5818
|
+
const raw = await fs6.readFile(profileFp, "utf8");
|
|
5819
|
+
const result = safeParse(raw);
|
|
5820
|
+
if (!result.ok || !isPlainRecord(result.value)) {
|
|
5821
|
+
this.logWarn("Profile config parse failed \u2014 falling back to defaults", {
|
|
5822
|
+
event: "config.profile_parse_failed",
|
|
5823
|
+
path: profileFp
|
|
5824
|
+
});
|
|
5825
|
+
parsed = {};
|
|
5826
|
+
} else {
|
|
5827
|
+
parsed = result.value;
|
|
5828
|
+
}
|
|
5829
|
+
} catch (err) {
|
|
5830
|
+
if (err.code !== "ENOENT") {
|
|
5831
|
+
this.logWarn("Profile config read failed", {
|
|
5832
|
+
event: "config.profile_read_failed",
|
|
5833
|
+
path: profileFp,
|
|
5834
|
+
message: toErrorMessage(err)
|
|
5835
|
+
});
|
|
5836
|
+
return;
|
|
5837
|
+
}
|
|
5838
|
+
existed = false;
|
|
5839
|
+
parsed = {};
|
|
5840
|
+
}
|
|
5841
|
+
let seed;
|
|
5842
|
+
if (!existed && oldFileExisted && Object.keys(oldGlobalParsed).length >= 1) {
|
|
5843
|
+
seed = { ...oldGlobalParsed };
|
|
5844
|
+
delete seed["activeProfile"];
|
|
5845
|
+
const filled = fillMissingDefaults(seed, BEHAVIOR_DEFAULTS);
|
|
5846
|
+
seed = filled.value;
|
|
5847
|
+
} else {
|
|
5848
|
+
const filled = fillMissingDefaults(parsed, BEHAVIOR_DEFAULTS);
|
|
5849
|
+
if (!filled.changed) return;
|
|
5850
|
+
seed = filled.value;
|
|
5851
|
+
}
|
|
5852
|
+
await atomicWrite(profileFp, JSON.stringify(seed, null, 2), { mode: 384 });
|
|
5540
5853
|
this.events?.emit("storage.write", {
|
|
5541
5854
|
sessionId: "~config~",
|
|
5542
5855
|
store: "config",
|
|
5543
|
-
filePath:
|
|
5544
|
-
operation: "
|
|
5856
|
+
filePath: profileFp,
|
|
5857
|
+
operation: "ensure_profile_defaults",
|
|
5545
5858
|
outcome: "success",
|
|
5546
5859
|
durationMs: Date.now() - t0,
|
|
5547
5860
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
@@ -5551,18 +5864,19 @@ var DefaultConfigLoader = class {
|
|
|
5551
5864
|
this.events?.emit("storage.error", {
|
|
5552
5865
|
sessionId: "~config~",
|
|
5553
5866
|
store: "config",
|
|
5554
|
-
filePath:
|
|
5555
|
-
operation: "
|
|
5867
|
+
filePath: profileFp,
|
|
5868
|
+
operation: "ensure_profile_defaults",
|
|
5556
5869
|
outcome: "failure",
|
|
5557
5870
|
error: storageErrorString(err),
|
|
5558
5871
|
recoverable: false,
|
|
5559
5872
|
durationMs: Date.now() - t0,
|
|
5560
5873
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5561
5874
|
});
|
|
5562
|
-
this.logWarn(
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5875
|
+
this.logWarn("Profile config defaults write failed", {
|
|
5876
|
+
event: "config.profile_write_failed",
|
|
5877
|
+
path: profileFp,
|
|
5878
|
+
message: toErrorMessage(err)
|
|
5879
|
+
});
|
|
5566
5880
|
}
|
|
5567
5881
|
}
|
|
5568
5882
|
/**
|
|
@@ -5664,10 +5978,10 @@ var DefaultConfigLoader = class {
|
|
|
5664
5978
|
error: storageErrorString(err),
|
|
5665
5979
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5666
5980
|
});
|
|
5667
|
-
this.logWarn(
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
);
|
|
5981
|
+
this.logWarn("Config sync load failed", {
|
|
5982
|
+
event: "config.sync_load_failed",
|
|
5983
|
+
message: toErrorMessage(err)
|
|
5984
|
+
});
|
|
5671
5985
|
return null;
|
|
5672
5986
|
}
|
|
5673
5987
|
}
|
|
@@ -5696,10 +6010,11 @@ var DefaultConfigLoader = class {
|
|
|
5696
6010
|
error: storageErrorString(err),
|
|
5697
6011
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5698
6012
|
});
|
|
5699
|
-
this.logWarn(
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
6013
|
+
this.logWarn("Config read failed (stat)", {
|
|
6014
|
+
event: "config.read_failed",
|
|
6015
|
+
path: file,
|
|
6016
|
+
message: toErrorMessage(err)
|
|
6017
|
+
});
|
|
5703
6018
|
return {};
|
|
5704
6019
|
}
|
|
5705
6020
|
let raw;
|
|
@@ -5717,10 +6032,11 @@ var DefaultConfigLoader = class {
|
|
|
5717
6032
|
error: storageErrorString(err),
|
|
5718
6033
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5719
6034
|
});
|
|
5720
|
-
this.logWarn(
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
6035
|
+
this.logWarn("Config read failed (read)", {
|
|
6036
|
+
event: "config.read_failed",
|
|
6037
|
+
path: file,
|
|
6038
|
+
message: toErrorMessage(err)
|
|
6039
|
+
});
|
|
5724
6040
|
}
|
|
5725
6041
|
this.jsonCache.set(file, { mtimeMs: null, value: {} });
|
|
5726
6042
|
return {};
|
|
@@ -5737,10 +6053,10 @@ var DefaultConfigLoader = class {
|
|
|
5737
6053
|
error: "parse error or empty file",
|
|
5738
6054
|
...this.traceId !== void 0 ? { traceId: this.traceId } : {}
|
|
5739
6055
|
});
|
|
5740
|
-
this.logWarn(
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
);
|
|
6056
|
+
this.logWarn("Config parse failed \u2014 falling back to defaults", {
|
|
6057
|
+
event: "config.parse_failed",
|
|
6058
|
+
path: file
|
|
6059
|
+
});
|
|
5744
6060
|
return {};
|
|
5745
6061
|
}
|
|
5746
6062
|
this.jsonCache.set(file, { mtimeMs, value: structuredClone(parsed.value) });
|
|
@@ -7701,9 +8017,9 @@ var SessionAnalyzer = class {
|
|
|
7701
8017
|
};
|
|
7702
8018
|
|
|
7703
8019
|
// src/session-registry.ts
|
|
8020
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7704
8021
|
import * as fs12 from "node:fs/promises";
|
|
7705
8022
|
import * as path17 from "node:path";
|
|
7706
|
-
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7707
8023
|
var REGISTRY_FILE = "session-registry.json";
|
|
7708
8024
|
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
7709
8025
|
var STALE_TIMEOUT_MS = 3e4;
|
|
@@ -8075,6 +8391,201 @@ var AGENT_REAP_MS = 3e4;
|
|
|
8075
8391
|
var AGENT_SWEEP_INTERVAL_MS = 1e4;
|
|
8076
8392
|
var PARTIAL_TEXT_CAP = 1200;
|
|
8077
8393
|
var PARTIAL_FLUSH_THROTTLE_MS = 300;
|
|
8394
|
+
var RECENT_TOOL_LIMIT = 12;
|
|
8395
|
+
var RECENT_MAIL_LIMIT = 12;
|
|
8396
|
+
var TOOL_TEXT_CAP = 360;
|
|
8397
|
+
var TOUCHED_FILE_LIMIT = 200;
|
|
8398
|
+
var TASK_TEXT_CAP = 1200;
|
|
8399
|
+
var PROMPT_TEXT_CAP = 6e3;
|
|
8400
|
+
var TODO_TEXT_CAP = 360;
|
|
8401
|
+
var TODO_LIMIT = 32;
|
|
8402
|
+
function lineCount(value) {
|
|
8403
|
+
return value.length === 0 ? 0 : value.split(/\r?\n/).length;
|
|
8404
|
+
}
|
|
8405
|
+
function patchDelta(value) {
|
|
8406
|
+
let addedLines = 0;
|
|
8407
|
+
let removedLines = 0;
|
|
8408
|
+
const hunkStart = value.indexOf("@@");
|
|
8409
|
+
if (hunkStart === -1) return { addedLines, removedLines };
|
|
8410
|
+
for (const line of value.slice(hunkStart).split(/\r?\n/)) {
|
|
8411
|
+
if (line.startsWith("+++") || line.startsWith("---")) continue;
|
|
8412
|
+
if (line.startsWith("+")) addedLines += 1;
|
|
8413
|
+
else if (line.startsWith("-")) removedLines += 1;
|
|
8414
|
+
}
|
|
8415
|
+
return { addedLines, removedLines };
|
|
8416
|
+
}
|
|
8417
|
+
function compactText(value) {
|
|
8418
|
+
return value.length <= TOOL_TEXT_CAP ? value : `${value.slice(0, TOOL_TEXT_CAP - 1)}\u2026`;
|
|
8419
|
+
}
|
|
8420
|
+
function boundedText(value, cap) {
|
|
8421
|
+
const trimmed = value.trim();
|
|
8422
|
+
return trimmed.length <= cap ? trimmed : `${trimmed.slice(0, cap - 1)}\u2026`;
|
|
8423
|
+
}
|
|
8424
|
+
function compactTodos(value) {
|
|
8425
|
+
if (!Array.isArray(value)) return void 0;
|
|
8426
|
+
const todos = [];
|
|
8427
|
+
for (const candidate of value) {
|
|
8428
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
8429
|
+
const todo = candidate;
|
|
8430
|
+
const id = typeof todo["id"] === "string" ? todo["id"].trim() : "";
|
|
8431
|
+
const content = typeof todo["content"] === "string" ? boundedText(todo["content"], TODO_TEXT_CAP) : "";
|
|
8432
|
+
const status = todo["status"];
|
|
8433
|
+
if (!id || !content || status !== "pending" && status !== "in_progress" && status !== "completed") {
|
|
8434
|
+
continue;
|
|
8435
|
+
}
|
|
8436
|
+
const activeForm = typeof todo["activeForm"] === "string" ? boundedText(todo["activeForm"], TODO_TEXT_CAP) : void 0;
|
|
8437
|
+
todos.push({ id, content, status, ...activeForm ? { activeForm } : {} });
|
|
8438
|
+
if (todos.length >= TODO_LIMIT) break;
|
|
8439
|
+
}
|
|
8440
|
+
return todos;
|
|
8441
|
+
}
|
|
8442
|
+
function compactToolInput(input) {
|
|
8443
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
8444
|
+
return typeof input === "string" ? { input: compactText(input) } : {};
|
|
8445
|
+
}
|
|
8446
|
+
const source = input;
|
|
8447
|
+
const safe = {};
|
|
8448
|
+
const safeKeys = [
|
|
8449
|
+
"file_path",
|
|
8450
|
+
"filePath",
|
|
8451
|
+
"path",
|
|
8452
|
+
"filename",
|
|
8453
|
+
"target_file",
|
|
8454
|
+
"targetFile",
|
|
8455
|
+
"line",
|
|
8456
|
+
"start_line",
|
|
8457
|
+
"startLine",
|
|
8458
|
+
"end_line",
|
|
8459
|
+
"endLine",
|
|
8460
|
+
"offset",
|
|
8461
|
+
"limit",
|
|
8462
|
+
"command",
|
|
8463
|
+
"cmd",
|
|
8464
|
+
"url",
|
|
8465
|
+
"href",
|
|
8466
|
+
"uri",
|
|
8467
|
+
"query",
|
|
8468
|
+
"pattern"
|
|
8469
|
+
];
|
|
8470
|
+
for (const key of safeKeys) {
|
|
8471
|
+
const value = source[key];
|
|
8472
|
+
if (typeof value === "string") safe[key] = compactText(value);
|
|
8473
|
+
else if (typeof value === "number" || typeof value === "boolean") safe[key] = value;
|
|
8474
|
+
}
|
|
8475
|
+
const content = [source["content"], source["text"], source["data"]].find(
|
|
8476
|
+
(value) => typeof value === "string"
|
|
8477
|
+
);
|
|
8478
|
+
const oldText = [source["old_string"], source["oldString"], source["search"]].find(
|
|
8479
|
+
(value) => typeof value === "string"
|
|
8480
|
+
);
|
|
8481
|
+
const newText = [source["new_string"], source["newString"], source["replacement"]].find(
|
|
8482
|
+
(value) => typeof value === "string"
|
|
8483
|
+
);
|
|
8484
|
+
const patch = [source["patch"], source["diff"]].find(
|
|
8485
|
+
(value) => typeof value === "string"
|
|
8486
|
+
);
|
|
8487
|
+
const delta = patch ? patchDelta(patch) : void 0;
|
|
8488
|
+
return {
|
|
8489
|
+
...Object.keys(safe).length > 0 ? { input: safe } : {},
|
|
8490
|
+
...content !== void 0 ? { inputLines: lineCount(content) } : {},
|
|
8491
|
+
...oldText !== void 0 ? { oldLines: lineCount(oldText) } : {},
|
|
8492
|
+
...newText !== void 0 ? { newLines: lineCount(newText) } : {},
|
|
8493
|
+
...delta && delta.addedLines > 0 ? { addedLines: delta.addedLines } : {},
|
|
8494
|
+
...delta && delta.removedLines > 0 ? { removedLines: delta.removedLines } : {}
|
|
8495
|
+
};
|
|
8496
|
+
}
|
|
8497
|
+
function completedToolReceipt(payload, pending) {
|
|
8498
|
+
const completedAt = Date.now();
|
|
8499
|
+
const durationMs = Math.max(
|
|
8500
|
+
0,
|
|
8501
|
+
payload.durationMs ?? completedAt - (pending?.startedAt ?? completedAt)
|
|
8502
|
+
);
|
|
8503
|
+
const compact = compactToolInput(payload.input ?? pending?.input);
|
|
8504
|
+
return {
|
|
8505
|
+
id: payload.id ?? `${payload.name}:${completedAt}`,
|
|
8506
|
+
name: payload.name,
|
|
8507
|
+
startedAt: pending?.startedAt ?? Math.max(0, completedAt - durationMs),
|
|
8508
|
+
completedAt,
|
|
8509
|
+
durationMs,
|
|
8510
|
+
ok: payload.ok !== false,
|
|
8511
|
+
...compact,
|
|
8512
|
+
...payload.output !== void 0 ? { output: compactText(payload.output) } : {},
|
|
8513
|
+
...payload.outputLines !== void 0 ? { outputLines: payload.outputLines } : {},
|
|
8514
|
+
...payload.outputBytes !== void 0 ? { outputBytes: payload.outputBytes } : {},
|
|
8515
|
+
...payload.outputTokens !== void 0 ? { outputTokens: payload.outputTokens } : {}
|
|
8516
|
+
};
|
|
8517
|
+
}
|
|
8518
|
+
function emptyActivityTotals() {
|
|
8519
|
+
return {
|
|
8520
|
+
filesTouched: [],
|
|
8521
|
+
reads: 0,
|
|
8522
|
+
writes: 0,
|
|
8523
|
+
edits: 0,
|
|
8524
|
+
terminalCalls: 0,
|
|
8525
|
+
webCalls: 0,
|
|
8526
|
+
searches: 0,
|
|
8527
|
+
otherCalls: 0,
|
|
8528
|
+
mailReceived: 0,
|
|
8529
|
+
mailSent: 0,
|
|
8530
|
+
linesRead: 0,
|
|
8531
|
+
linesWritten: 0,
|
|
8532
|
+
linesAdded: 0,
|
|
8533
|
+
linesRemoved: 0
|
|
8534
|
+
};
|
|
8535
|
+
}
|
|
8536
|
+
function addMailTotal(current, direction) {
|
|
8537
|
+
const next = { ...current ?? emptyActivityTotals() };
|
|
8538
|
+
if (direction === "incoming") next.mailReceived += 1;
|
|
8539
|
+
else next.mailSent += 1;
|
|
8540
|
+
return next;
|
|
8541
|
+
}
|
|
8542
|
+
function toolActivityKind(name) {
|
|
8543
|
+
const normalized = name.toLowerCase().replace(/[.:/-]+/g, "_");
|
|
8544
|
+
if (/^(read|view|open_file|read_file)|file_read/.test(normalized)) return "read";
|
|
8545
|
+
if (/^(write|create|save)|file_write/.test(normalized)) return "write";
|
|
8546
|
+
if (/edit|update|patch|replace|apply_patch/.test(normalized)) return "edit";
|
|
8547
|
+
if (/bash|shell|terminal|exec|command|powershell|cmd/.test(normalized)) return "terminal";
|
|
8548
|
+
if (/fetch|browser|browse|http|url|web/.test(normalized)) return "web";
|
|
8549
|
+
if (/search|grep|find|glob|query/.test(normalized)) return "search";
|
|
8550
|
+
return "other";
|
|
8551
|
+
}
|
|
8552
|
+
function receiptFilePath(receipt) {
|
|
8553
|
+
if (!receipt.input || typeof receipt.input !== "object" || Array.isArray(receipt.input)) {
|
|
8554
|
+
return void 0;
|
|
8555
|
+
}
|
|
8556
|
+
const input = receipt.input;
|
|
8557
|
+
for (const key of ["file_path", "filePath", "path", "filename", "target_file", "targetFile"]) {
|
|
8558
|
+
const value = input[key];
|
|
8559
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
8560
|
+
}
|
|
8561
|
+
return void 0;
|
|
8562
|
+
}
|
|
8563
|
+
function addToolActivity(current, receipt) {
|
|
8564
|
+
const next = {
|
|
8565
|
+
...current ?? emptyActivityTotals(),
|
|
8566
|
+
filesTouched: [...current?.filesTouched ?? []]
|
|
8567
|
+
};
|
|
8568
|
+
const kind = toolActivityKind(receipt.name);
|
|
8569
|
+
if (kind === "read") {
|
|
8570
|
+
next.reads += 1;
|
|
8571
|
+
next.linesRead += receipt.outputLines ?? 0;
|
|
8572
|
+
} else if (kind === "write") {
|
|
8573
|
+
next.writes += 1;
|
|
8574
|
+
next.linesWritten += receipt.inputLines ?? 0;
|
|
8575
|
+
} else if (kind === "edit") {
|
|
8576
|
+
next.edits += 1;
|
|
8577
|
+
next.linesAdded += receipt.addedLines ?? receipt.newLines ?? 0;
|
|
8578
|
+
next.linesRemoved += receipt.removedLines ?? receipt.oldLines ?? 0;
|
|
8579
|
+
} else if (kind === "terminal") next.terminalCalls += 1;
|
|
8580
|
+
else if (kind === "web") next.webCalls += 1;
|
|
8581
|
+
else if (kind === "search") next.searches += 1;
|
|
8582
|
+
else next.otherCalls += 1;
|
|
8583
|
+
const filePath = receiptFilePath(receipt);
|
|
8584
|
+
if (filePath && !next.filesTouched.includes(filePath) && next.filesTouched.length < TOUCHED_FILE_LIMIT) {
|
|
8585
|
+
next.filesTouched.push(filePath);
|
|
8586
|
+
}
|
|
8587
|
+
return next;
|
|
8588
|
+
}
|
|
8078
8589
|
function clampPct(pct) {
|
|
8079
8590
|
if (!Number.isFinite(pct)) return 0;
|
|
8080
8591
|
return Math.max(0, Math.min(100, pct));
|
|
@@ -8092,6 +8603,7 @@ var AgentStatusTracker = class {
|
|
|
8092
8603
|
// Leader tracking
|
|
8093
8604
|
leaderStatus = "idle";
|
|
8094
8605
|
leaderCurrentTool;
|
|
8606
|
+
leaderCurrentTask;
|
|
8095
8607
|
leaderIterations = 0;
|
|
8096
8608
|
leaderToolCalls = 0;
|
|
8097
8609
|
leaderCostUsd = 0;
|
|
@@ -8101,10 +8613,22 @@ var AgentStatusTracker = class {
|
|
|
8101
8613
|
leaderModel;
|
|
8102
8614
|
leaderPartialText = "";
|
|
8103
8615
|
leaderStartedAt;
|
|
8616
|
+
leaderRecentTools = [];
|
|
8617
|
+
leaderRecentMail = [];
|
|
8618
|
+
leaderTodos = [];
|
|
8619
|
+
leaderLatestPrompt;
|
|
8620
|
+
leaderLatestPromptAt;
|
|
8621
|
+
leaderActivity = emptyActivityTotals();
|
|
8622
|
+
leaderPendingTools = /* @__PURE__ */ new Map();
|
|
8623
|
+
subagentPendingTools = /* @__PURE__ */ new Map();
|
|
8624
|
+
seenIncomingMail = /* @__PURE__ */ new Set();
|
|
8625
|
+
seenOutgoingMail = /* @__PURE__ */ new Set();
|
|
8104
8626
|
unsubscribers = [];
|
|
8105
8627
|
onUpdate;
|
|
8106
8628
|
sweepTimer = null;
|
|
8107
8629
|
partialTimer = null;
|
|
8630
|
+
/** Serialize registry writes so concurrent flush() calls don't race. */
|
|
8631
|
+
flushPromise = null;
|
|
8108
8632
|
constructor(opts) {
|
|
8109
8633
|
this.events = opts.events;
|
|
8110
8634
|
this.registry = opts.registry;
|
|
@@ -8127,6 +8651,13 @@ var AgentStatusTracker = class {
|
|
|
8127
8651
|
this.markLeaderStarted(p?.at);
|
|
8128
8652
|
this.captureLeaderContext(p?.ctx);
|
|
8129
8653
|
if (p?.model) this.leaderModel = p.model;
|
|
8654
|
+
if (p?.inputText?.trim()) {
|
|
8655
|
+
const prompt = boundedText(p.inputText, PROMPT_TEXT_CAP);
|
|
8656
|
+
this.leaderLatestPrompt = prompt;
|
|
8657
|
+
this.leaderLatestPromptAt = p.at ? Date.parse(p.at) : Date.now();
|
|
8658
|
+
if (!Number.isFinite(this.leaderLatestPromptAt)) this.leaderLatestPromptAt = Date.now();
|
|
8659
|
+
this.leaderCurrentTask = boundedText(p.inputText, TASK_TEXT_CAP);
|
|
8660
|
+
}
|
|
8130
8661
|
this.leaderStatus = "running";
|
|
8131
8662
|
this.leaderIterations++;
|
|
8132
8663
|
this.flush();
|
|
@@ -8155,6 +8686,7 @@ var AgentStatusTracker = class {
|
|
|
8155
8686
|
this.captureLeaderContext(p?.ctx);
|
|
8156
8687
|
this.leaderStatus = p?.status === "failed" ? "error" : "idle";
|
|
8157
8688
|
this.leaderCurrentTool = void 0;
|
|
8689
|
+
this.leaderCurrentTask = void 0;
|
|
8158
8690
|
this.leaderPartialText = "";
|
|
8159
8691
|
if (this.leaderStatus === "idle") this.leaderStartedAt = void 0;
|
|
8160
8692
|
this.flush();
|
|
@@ -8166,10 +8698,18 @@ var AgentStatusTracker = class {
|
|
|
8166
8698
|
this.captureLeaderContext(p?.ctx);
|
|
8167
8699
|
this.leaderStatus = "error";
|
|
8168
8700
|
this.leaderCurrentTool = void 0;
|
|
8701
|
+
this.leaderCurrentTask = void 0;
|
|
8169
8702
|
this.leaderPartialText = "";
|
|
8170
8703
|
this.flush();
|
|
8171
8704
|
})
|
|
8172
8705
|
);
|
|
8706
|
+
this.unsubscribers.push(
|
|
8707
|
+
on("iteration.completed", (_event, payload) => {
|
|
8708
|
+
const p = payload;
|
|
8709
|
+
this.captureLeaderContext(p?.ctx);
|
|
8710
|
+
this.flush();
|
|
8711
|
+
})
|
|
8712
|
+
);
|
|
8173
8713
|
this.unsubscribers.push(
|
|
8174
8714
|
on("tool.started", (_event, payload) => {
|
|
8175
8715
|
const p = payload;
|
|
@@ -8177,13 +8717,26 @@ var AgentStatusTracker = class {
|
|
|
8177
8717
|
this.markLeaderStarted();
|
|
8178
8718
|
this.leaderCurrentTool = p.name;
|
|
8179
8719
|
this.leaderToolCalls++;
|
|
8720
|
+
this.leaderPendingTools.set(p.id ?? p.name, {
|
|
8721
|
+
name: p.name,
|
|
8722
|
+
input: p.input,
|
|
8723
|
+
startedAt: Date.now()
|
|
8724
|
+
});
|
|
8180
8725
|
}
|
|
8181
8726
|
this.leaderStatus = "running";
|
|
8182
8727
|
this.flush();
|
|
8183
8728
|
})
|
|
8184
8729
|
);
|
|
8185
8730
|
this.unsubscribers.push(
|
|
8186
|
-
on("tool.executed", () => {
|
|
8731
|
+
on("tool.executed", (_event, payload) => {
|
|
8732
|
+
const p = payload;
|
|
8733
|
+
if (p?.name) {
|
|
8734
|
+
const key = p.id ?? p.name;
|
|
8735
|
+
const receipt = completedToolReceipt(p, this.leaderPendingTools.get(key));
|
|
8736
|
+
this.leaderRecentTools = [receipt, ...this.leaderRecentTools].slice(0, RECENT_TOOL_LIMIT);
|
|
8737
|
+
this.leaderActivity = addToolActivity(this.leaderActivity, receipt);
|
|
8738
|
+
this.leaderPendingTools.delete(key);
|
|
8739
|
+
}
|
|
8187
8740
|
this.leaderCurrentTool = void 0;
|
|
8188
8741
|
this.flush();
|
|
8189
8742
|
})
|
|
@@ -8195,6 +8748,36 @@ var AgentStatusTracker = class {
|
|
|
8195
8748
|
this.flush();
|
|
8196
8749
|
})
|
|
8197
8750
|
);
|
|
8751
|
+
const recordMail = (direction, payload) => {
|
|
8752
|
+
const p = payload;
|
|
8753
|
+
if (!p?.messageId) return;
|
|
8754
|
+
const seen = direction === "incoming" ? this.seenIncomingMail : this.seenOutgoingMail;
|
|
8755
|
+
if (seen.has(p.messageId)) return;
|
|
8756
|
+
seen.add(p.messageId);
|
|
8757
|
+
const receipt = {
|
|
8758
|
+
id: p.messageId,
|
|
8759
|
+
direction,
|
|
8760
|
+
from: p.from ?? "?",
|
|
8761
|
+
to: p.to ?? (direction === "incoming" ? "leader" : "?"),
|
|
8762
|
+
type: p.type ?? "note",
|
|
8763
|
+
subject: p.subject ?? "Message",
|
|
8764
|
+
at: Date.now()
|
|
8765
|
+
};
|
|
8766
|
+
const directAgentId = direction === "outgoing" ? p.from : p.to;
|
|
8767
|
+
const entry = directAgentId ? this.agents.get(directAgentId) : void 0;
|
|
8768
|
+
if (entry) {
|
|
8769
|
+
entry.recentMail = [receipt, ...entry.recentMail ?? []].slice(0, RECENT_MAIL_LIMIT);
|
|
8770
|
+
entry.activity = addMailTotal(entry.activity, direction);
|
|
8771
|
+
} else {
|
|
8772
|
+
this.leaderRecentMail = [receipt, ...this.leaderRecentMail].slice(0, RECENT_MAIL_LIMIT);
|
|
8773
|
+
this.leaderActivity = addMailTotal(this.leaderActivity, direction);
|
|
8774
|
+
}
|
|
8775
|
+
this.flush();
|
|
8776
|
+
};
|
|
8777
|
+
this.unsubscribers.push(
|
|
8778
|
+
on("mailbox.message_sent", (_event, payload) => recordMail("outgoing", payload)),
|
|
8779
|
+
on("mailbox.received", (_event, payload) => recordMail("incoming", payload))
|
|
8780
|
+
);
|
|
8198
8781
|
this.unsubscribers.push(
|
|
8199
8782
|
on("llm.stream_started", () => {
|
|
8200
8783
|
this.markLeaderStarted();
|
|
@@ -8255,7 +8838,15 @@ var AgentStatusTracker = class {
|
|
|
8255
8838
|
let entry = this.agents.get(id);
|
|
8256
8839
|
if (!entry) {
|
|
8257
8840
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8258
|
-
entry = {
|
|
8841
|
+
entry = {
|
|
8842
|
+
id,
|
|
8843
|
+
name: id,
|
|
8844
|
+
status: "idle",
|
|
8845
|
+
iterations: 0,
|
|
8846
|
+
toolCalls: 0,
|
|
8847
|
+
startedAt: now,
|
|
8848
|
+
lastActivityAt: now
|
|
8849
|
+
};
|
|
8259
8850
|
this.agents.set(id, entry);
|
|
8260
8851
|
}
|
|
8261
8852
|
entry.lastActivityAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -8268,6 +8859,8 @@ var AgentStatusTracker = class {
|
|
|
8268
8859
|
const entry = touch(p.subagentId);
|
|
8269
8860
|
entry.name = p.name?.trim() || entry.name;
|
|
8270
8861
|
if (p.model) entry.model = p.model;
|
|
8862
|
+
if (p.taskId) entry.taskId = p.taskId;
|
|
8863
|
+
if (p.description?.trim()) entry.currentTask = boundedText(p.description, TASK_TEXT_CAP);
|
|
8271
8864
|
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8272
8865
|
entry.status = "running";
|
|
8273
8866
|
this.flush();
|
|
@@ -8288,19 +8881,41 @@ var AgentStatusTracker = class {
|
|
|
8288
8881
|
if (!p?.subagentId) return;
|
|
8289
8882
|
const entry = touch(p.subagentId);
|
|
8290
8883
|
entry.status = "running";
|
|
8884
|
+
if (p.taskId) entry.taskId = p.taskId;
|
|
8885
|
+
if (p.description?.trim()) entry.currentTask = boundedText(p.description, TASK_TEXT_CAP);
|
|
8291
8886
|
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8292
8887
|
entry.iterations++;
|
|
8293
8888
|
this.flush();
|
|
8294
8889
|
})
|
|
8295
8890
|
);
|
|
8891
|
+
this.unsubscribers.push(
|
|
8892
|
+
on("subagent.tool_started", (_e, payload) => {
|
|
8893
|
+
const p = payload;
|
|
8894
|
+
if (!p?.subagentId || !p.name) return;
|
|
8895
|
+
const entry = touch(p.subagentId);
|
|
8896
|
+
entry.status = "running";
|
|
8897
|
+
entry.currentTool = p.name;
|
|
8898
|
+
this.subagentPendingTools.set(`${p.subagentId}:${p.id ?? p.name}`, {
|
|
8899
|
+
name: p.name,
|
|
8900
|
+
input: p.input,
|
|
8901
|
+
startedAt: Date.now()
|
|
8902
|
+
});
|
|
8903
|
+
this.flush();
|
|
8904
|
+
})
|
|
8905
|
+
);
|
|
8296
8906
|
this.unsubscribers.push(
|
|
8297
8907
|
on("subagent.tool_executed", (_e, payload) => {
|
|
8298
8908
|
const p = payload;
|
|
8299
|
-
if (!p?.subagentId) return;
|
|
8909
|
+
if (!p?.subagentId || !p.name) return;
|
|
8300
8910
|
const entry = touch(p.subagentId);
|
|
8301
8911
|
entry.status = "running";
|
|
8302
8912
|
if (!entry.startedAt) entry.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
8303
|
-
|
|
8913
|
+
const key = `${p.subagentId}:${p.id ?? p.name}`;
|
|
8914
|
+
const receipt = completedToolReceipt(p, this.subagentPendingTools.get(key));
|
|
8915
|
+
entry.recentTools = [receipt, ...entry.recentTools ?? []].slice(0, RECENT_TOOL_LIMIT);
|
|
8916
|
+
entry.activity = addToolActivity(entry.activity, receipt);
|
|
8917
|
+
this.subagentPendingTools.delete(key);
|
|
8918
|
+
entry.currentTool = void 0;
|
|
8304
8919
|
entry.toolCalls++;
|
|
8305
8920
|
this.flush();
|
|
8306
8921
|
})
|
|
@@ -8330,6 +8945,8 @@ var AgentStatusTracker = class {
|
|
|
8330
8945
|
if (!entry) return;
|
|
8331
8946
|
entry.status = p.status === "failed" || p.status === "timeout" ? "error" : "idle";
|
|
8332
8947
|
entry.currentTool = void 0;
|
|
8948
|
+
entry.currentTask = void 0;
|
|
8949
|
+
entry.taskId = void 0;
|
|
8333
8950
|
entry.partialText = void 0;
|
|
8334
8951
|
if (typeof p.iterations === "number") entry.iterations = p.iterations;
|
|
8335
8952
|
if (typeof p.toolCalls === "number") entry.toolCalls = p.toolCalls;
|
|
@@ -8409,6 +9026,7 @@ var AgentStatusTracker = class {
|
|
|
8409
9026
|
startedAt: this.leaderStartedAt,
|
|
8410
9027
|
status: this.leaderStatus,
|
|
8411
9028
|
currentTool: this.leaderCurrentTool,
|
|
9029
|
+
currentTask: this.leaderCurrentTask,
|
|
8412
9030
|
iterations: this.leaderIterations,
|
|
8413
9031
|
toolCalls: this.leaderToolCalls,
|
|
8414
9032
|
costUsd: this.leaderCostUsd,
|
|
@@ -8417,6 +9035,12 @@ var AgentStatusTracker = class {
|
|
|
8417
9035
|
ctxPct: this.leaderCtxPct,
|
|
8418
9036
|
model: this.leaderModel,
|
|
8419
9037
|
partialText: this.leaderPartialText || void 0,
|
|
9038
|
+
recentTools: this.leaderRecentTools,
|
|
9039
|
+
recentMail: this.leaderRecentMail,
|
|
9040
|
+
todos: this.leaderTodos,
|
|
9041
|
+
latestPrompt: this.leaderLatestPrompt,
|
|
9042
|
+
latestPromptAt: this.leaderLatestPromptAt,
|
|
9043
|
+
activity: this.leaderActivity,
|
|
8420
9044
|
lastActivityAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
8421
9045
|
};
|
|
8422
9046
|
const allAgents = [leaderEntry, ...this.agents.values()];
|
|
@@ -8428,12 +9052,21 @@ var AgentStatusTracker = class {
|
|
|
8428
9052
|
});
|
|
8429
9053
|
} catch {
|
|
8430
9054
|
}
|
|
8431
|
-
this.registry.updateAgents(allAgents)
|
|
9055
|
+
const write = this.registry.updateAgents(allAgents);
|
|
9056
|
+
const chain = write.then(() => {
|
|
8432
9057
|
try {
|
|
8433
9058
|
this.onUpdate?.();
|
|
8434
9059
|
} catch {
|
|
8435
9060
|
}
|
|
8436
|
-
})
|
|
9061
|
+
});
|
|
9062
|
+
if (this.flushPromise) {
|
|
9063
|
+
this.flushPromise = this.flushPromise.then(
|
|
9064
|
+
() => chain,
|
|
9065
|
+
() => chain
|
|
9066
|
+
).catch(() => void 0);
|
|
9067
|
+
} else {
|
|
9068
|
+
this.flushPromise = chain.catch(() => void 0);
|
|
9069
|
+
}
|
|
8437
9070
|
}
|
|
8438
9071
|
currentSessionId() {
|
|
8439
9072
|
return typeof this.sessionId === "function" ? this.sessionId() : this.sessionId;
|
|
@@ -8455,6 +9088,8 @@ var AgentStatusTracker = class {
|
|
|
8455
9088
|
if (typeof ctx !== "object" || ctx === null) return;
|
|
8456
9089
|
const c = ctx;
|
|
8457
9090
|
if (typeof c.model === "string" && c.model.length > 0) this.leaderModel = c.model;
|
|
9091
|
+
const todos = compactTodos(c.todos);
|
|
9092
|
+
if (todos !== void 0) this.leaderTodos = todos;
|
|
8458
9093
|
const metaLimit = c.meta?.["effectiveMaxContext"];
|
|
8459
9094
|
const providerMax = c.provider?.capabilities?.maxContext;
|
|
8460
9095
|
const maxContext = typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : void 0;
|
|
@@ -8610,9 +9245,6 @@ var DefaultSessionRewinder = class {
|
|
|
8610
9245
|
const snapshotsToRevert = [];
|
|
8611
9246
|
for (let i = targetIdx + 1; i < events.length; i++) {
|
|
8612
9247
|
const event = expectDefined(events[i]);
|
|
8613
|
-
if (event.type === "checkpoint") {
|
|
8614
|
-
break;
|
|
8615
|
-
}
|
|
8616
9248
|
if (event.type === "file_snapshot") {
|
|
8617
9249
|
const snapshotEvent = event;
|
|
8618
9250
|
if (snapshotEvent.promptIndex >= checkpointIndex) {
|
|
@@ -8634,11 +9266,11 @@ var DefaultSessionRewinder = class {
|
|
|
8634
9266
|
checkpoints.push({ promptIndex: event.promptIndex, ts: event.ts });
|
|
8635
9267
|
}
|
|
8636
9268
|
}
|
|
8637
|
-
if (checkpoints.length === 0) {
|
|
9269
|
+
if (checkpoints.length === 0 || n <= 0) {
|
|
8638
9270
|
return { revertedFiles: [], errors: [], toPromptIndex: 0, removedEvents: 0 };
|
|
8639
9271
|
}
|
|
8640
9272
|
checkpoints.sort((a, b) => b.promptIndex - a.promptIndex);
|
|
8641
|
-
const targetIndex = checkpoints[n]?.promptIndex ?? 0;
|
|
9273
|
+
const targetIndex = checkpoints[n - 1]?.promptIndex ?? 0;
|
|
8642
9274
|
const snapshotsToRevert = [];
|
|
8643
9275
|
let shouldRevert = false;
|
|
8644
9276
|
for (const event of events) {
|
|
@@ -8719,6 +9351,16 @@ async function revertSnapshots(snapshots, projectRoot) {
|
|
|
8719
9351
|
return { revertedFiles, errors };
|
|
8720
9352
|
}
|
|
8721
9353
|
|
|
9354
|
+
// src/storage/session-rewind-apply.ts
|
|
9355
|
+
async function applyRewindToConversation(opts) {
|
|
9356
|
+
const { session, state, sessionsDir, promptIndex, revertedFiles } = opts;
|
|
9357
|
+
const removedEvents = await session.truncateToCheckpoint(promptIndex, revertedFiles ?? []);
|
|
9358
|
+
const store = new DefaultSessionStore({ dir: sessionsDir });
|
|
9359
|
+
const data = await store.load(session.id);
|
|
9360
|
+
state.replaceMessages(data.messages);
|
|
9361
|
+
return { removedEvents, messageCount: data.messages.length };
|
|
9362
|
+
}
|
|
9363
|
+
|
|
8722
9364
|
// src/storage/todos-checkpoint.ts
|
|
8723
9365
|
import * as fsp9 from "node:fs/promises";
|
|
8724
9366
|
async function loadTodosCheckpoint(filePath, events, traceId) {
|
|
@@ -10028,6 +10670,184 @@ function goalTag(goalFile) {
|
|
|
10028
10670
|
return `${GOAL_BOARD_TAG_PREFIX}${goal}`;
|
|
10029
10671
|
}
|
|
10030
10672
|
|
|
10673
|
+
// src/storage/goal-coordination.ts
|
|
10674
|
+
import { updateTask } from "@wrongstack/kanban";
|
|
10675
|
+
var DONE_MARKER = /\[done:\s*([^\]]+)\]/gi;
|
|
10676
|
+
var DONE_PREFIX = /^\s*(?:✅|\[[x✓]\]|\(done\))\s*/i;
|
|
10677
|
+
function parseCompletedGoalDeliverables(finalText, deliverables) {
|
|
10678
|
+
if (!finalText || deliverables.length === 0) return [];
|
|
10679
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10680
|
+
const completed = [];
|
|
10681
|
+
for (const match of finalText.matchAll(DONE_MARKER)) {
|
|
10682
|
+
const raw = match[1]?.trim();
|
|
10683
|
+
if (!raw) continue;
|
|
10684
|
+
const numeric = /^\d+$/.test(raw) ? Number.parseInt(raw, 10) : void 0;
|
|
10685
|
+
let index = numeric === void 0 ? -1 : numeric === 0 ? 0 : numeric - 1;
|
|
10686
|
+
if (index < 0 || index >= deliverables.length) {
|
|
10687
|
+
const prefix = raw.toLowerCase();
|
|
10688
|
+
index = deliverables.findIndex(
|
|
10689
|
+
(item) => stripGoalDeliverableMarker(item).toLowerCase().startsWith(prefix)
|
|
10690
|
+
);
|
|
10691
|
+
}
|
|
10692
|
+
if (index < 0 || index >= deliverables.length || seen.has(index)) continue;
|
|
10693
|
+
const source = deliverables[index] ?? "";
|
|
10694
|
+
if (isGoalDeliverableComplete(source)) continue;
|
|
10695
|
+
seen.add(index);
|
|
10696
|
+
completed.push({ index, text: stripGoalDeliverableMarker(source) });
|
|
10697
|
+
}
|
|
10698
|
+
return completed;
|
|
10699
|
+
}
|
|
10700
|
+
function isGoalDeliverableComplete(value) {
|
|
10701
|
+
return DONE_PREFIX.test(value);
|
|
10702
|
+
}
|
|
10703
|
+
function stripGoalDeliverableMarker(value) {
|
|
10704
|
+
return value.replace(DONE_PREFIX, "").trim();
|
|
10705
|
+
}
|
|
10706
|
+
function applyGoalDeliverableCompletions(goal, completed) {
|
|
10707
|
+
if (completed.length === 0) return recomputeGoalProgress(goal);
|
|
10708
|
+
const completedIndices = new Set(completed.map((item) => item.index));
|
|
10709
|
+
let next = {
|
|
10710
|
+
...goal,
|
|
10711
|
+
deliverables: (goal.deliverables ?? []).map(
|
|
10712
|
+
(item, index) => completedIndices.has(index) && !isGoalDeliverableComplete(item) ? `\u2705 ${item}` : item
|
|
10713
|
+
)
|
|
10714
|
+
};
|
|
10715
|
+
for (const item of completed) {
|
|
10716
|
+
next = appendJournal(next, {
|
|
10717
|
+
source: "deliverable",
|
|
10718
|
+
task: `Deliverable completed: ${item.text}`.slice(0, 240),
|
|
10719
|
+
status: "success",
|
|
10720
|
+
note: `[DONE: ${item.index + 1}]`
|
|
10721
|
+
});
|
|
10722
|
+
}
|
|
10723
|
+
return recomputeGoalProgress(next);
|
|
10724
|
+
}
|
|
10725
|
+
function recomputeGoalProgress(goal) {
|
|
10726
|
+
const deliverables = goal.deliverables ?? [];
|
|
10727
|
+
if (deliverables.length === 0) return goal;
|
|
10728
|
+
const done = deliverables.filter(isGoalDeliverableComplete).length;
|
|
10729
|
+
const progress = Math.round(done / deliverables.length * 100);
|
|
10730
|
+
return recordProgress(goal, progress, `${done}/${deliverables.length} deliverables complete`);
|
|
10731
|
+
}
|
|
10732
|
+
async function coordinateGoalIteration(options) {
|
|
10733
|
+
const current = await loadGoal(options.goalPath, options.events);
|
|
10734
|
+
if (!current) return null;
|
|
10735
|
+
const completed = parseCompletedGoalDeliverables(
|
|
10736
|
+
options.finalText,
|
|
10737
|
+
current.deliverables ?? []
|
|
10738
|
+
);
|
|
10739
|
+
let goal = applyGoalDeliverableCompletions(current, completed);
|
|
10740
|
+
await saveGoal(options.goalPath, goal, options.events);
|
|
10741
|
+
const kanbanUpdatedTaskIds = await refreshGoalKanban(
|
|
10742
|
+
options.projectRoot,
|
|
10743
|
+
goal,
|
|
10744
|
+
completed
|
|
10745
|
+
);
|
|
10746
|
+
goal = await recomputeGoalProgressFromKanban(options.projectRoot, goal);
|
|
10747
|
+
let reached = false;
|
|
10748
|
+
const allComplete = (goal.deliverables?.length ?? 0) > 0 && goal.deliverables?.every(isGoalDeliverableComplete) === true;
|
|
10749
|
+
if (allComplete && options.brain) {
|
|
10750
|
+
const decision = await options.brain.decide({
|
|
10751
|
+
id: `goal-reached-${goal.iterations}`,
|
|
10752
|
+
sessionId: options.sessionId,
|
|
10753
|
+
source: "system",
|
|
10754
|
+
question: "Have all goal deliverables been reached and verified?",
|
|
10755
|
+
context: [
|
|
10756
|
+
`Goal: ${goal.refinedGoal ?? goal.goal}`,
|
|
10757
|
+
`Progress: ${goal.progress ?? 0}%`,
|
|
10758
|
+
"Completed deliverables:",
|
|
10759
|
+
...(goal.deliverables ?? []).map(
|
|
10760
|
+
(item, index) => `${index + 1}. ${stripGoalDeliverableMarker(item)}`
|
|
10761
|
+
)
|
|
10762
|
+
].join("\n"),
|
|
10763
|
+
risk: "high",
|
|
10764
|
+
fallback: "continue",
|
|
10765
|
+
options: [
|
|
10766
|
+
{
|
|
10767
|
+
id: "goal_reached",
|
|
10768
|
+
label: "Goal reached",
|
|
10769
|
+
consequence: "Persist the completed goal and stop the eternal loop."
|
|
10770
|
+
},
|
|
10771
|
+
{
|
|
10772
|
+
id: "keep_working",
|
|
10773
|
+
label: "Keep working",
|
|
10774
|
+
consequence: "Leave the goal active for another iteration."
|
|
10775
|
+
}
|
|
10776
|
+
]
|
|
10777
|
+
});
|
|
10778
|
+
reached = decision.type === "answer" && decision.optionId === "goal_reached";
|
|
10779
|
+
}
|
|
10780
|
+
if (reached) {
|
|
10781
|
+
const reachedAt = (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
10782
|
+
goal = appendJournal(
|
|
10783
|
+
{
|
|
10784
|
+
...goal,
|
|
10785
|
+
goalState: "completed",
|
|
10786
|
+
engineState: "stopped",
|
|
10787
|
+
progress: 100,
|
|
10788
|
+
progressNote: "goal reached",
|
|
10789
|
+
reachedAt,
|
|
10790
|
+
reachedNote: "goal reached"
|
|
10791
|
+
},
|
|
10792
|
+
{
|
|
10793
|
+
source: "deliverable",
|
|
10794
|
+
task: "GOAL REACHED",
|
|
10795
|
+
status: "success",
|
|
10796
|
+
note: "goal reached"
|
|
10797
|
+
}
|
|
10798
|
+
);
|
|
10799
|
+
}
|
|
10800
|
+
await saveGoal(options.goalPath, goal, options.events);
|
|
10801
|
+
return { goal, completed, kanbanUpdatedTaskIds, reached };
|
|
10802
|
+
}
|
|
10803
|
+
async function resolveGoalBoard(projectRoot, goal) {
|
|
10804
|
+
const boardId = goal.kanbanBoardId;
|
|
10805
|
+
if (boardId) {
|
|
10806
|
+
const board = await findGoalKanbanBoard(projectRoot, boardId);
|
|
10807
|
+
if (board) return board;
|
|
10808
|
+
}
|
|
10809
|
+
return findGoalBoardByTag(projectRoot, goal);
|
|
10810
|
+
}
|
|
10811
|
+
async function refreshGoalKanban(projectRoot, goal, completed) {
|
|
10812
|
+
if (completed.length === 0) return [];
|
|
10813
|
+
const board = await resolveGoalBoard(projectRoot, goal);
|
|
10814
|
+
if (!board) return [];
|
|
10815
|
+
const doneColumn = board.columns.find((column) => column.title.toLowerCase() === "done");
|
|
10816
|
+
if (!doneColumn) return [];
|
|
10817
|
+
const completedTitles = new Set(completed.map((item) => normalizeTitle(item.text)));
|
|
10818
|
+
const updated = [];
|
|
10819
|
+
for (const task of board.tasks) {
|
|
10820
|
+
if (!completedTitles.has(normalizeTitle(task.title))) continue;
|
|
10821
|
+
const result = await updateTask(
|
|
10822
|
+
projectRoot,
|
|
10823
|
+
board.id,
|
|
10824
|
+
task.id,
|
|
10825
|
+
{ columnId: doneColumn.id, status: "completed" },
|
|
10826
|
+
{
|
|
10827
|
+
actor: "goal-coordinator",
|
|
10828
|
+
note: "deliverable-completed: Updated from [DONE:] marker."
|
|
10829
|
+
}
|
|
10830
|
+
);
|
|
10831
|
+
if (result) updated.push(task.id);
|
|
10832
|
+
}
|
|
10833
|
+
return updated;
|
|
10834
|
+
}
|
|
10835
|
+
async function recomputeGoalProgressFromKanban(projectRoot, goal) {
|
|
10836
|
+
const board = await resolveGoalBoard(projectRoot, goal);
|
|
10837
|
+
if (!board || !goal.deliverables?.length) return recomputeGoalProgress(goal);
|
|
10838
|
+
const completedTitles = new Set(
|
|
10839
|
+
board.tasks.filter((task) => task.status === "completed").map((task) => normalizeTitle(task.title))
|
|
10840
|
+
);
|
|
10841
|
+
const deliverables = goal.deliverables.map((item) => {
|
|
10842
|
+
if (isGoalDeliverableComplete(item)) return item;
|
|
10843
|
+
return completedTitles.has(normalizeTitle(item)) ? `\u2705 ${item}` : item;
|
|
10844
|
+
});
|
|
10845
|
+
return recomputeGoalProgress({ ...goal, deliverables });
|
|
10846
|
+
}
|
|
10847
|
+
function normalizeTitle(value) {
|
|
10848
|
+
return stripGoalDeliverableMarker(value).replace(/\s+/g, " ").trim().toLowerCase();
|
|
10849
|
+
}
|
|
10850
|
+
|
|
10031
10851
|
// src/storage/prompt-store.ts
|
|
10032
10852
|
import { createHash as createHash7 } from "node:crypto";
|
|
10033
10853
|
import * as fs14 from "node:fs/promises";
|
|
@@ -10646,6 +11466,9 @@ var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
10646
11466
|
"user_input",
|
|
10647
11467
|
"llm_response",
|
|
10648
11468
|
"tool_result",
|
|
11469
|
+
"message_appended",
|
|
11470
|
+
"message_updated",
|
|
11471
|
+
"messages_replaced",
|
|
10649
11472
|
"context_snapshot",
|
|
10650
11473
|
"checkpoint",
|
|
10651
11474
|
"file_snapshot",
|
|
@@ -10666,11 +11489,22 @@ var STANDARD_AUDIT_EVENTS = /* @__PURE__ */ new Set([
|
|
|
10666
11489
|
"provider_retry",
|
|
10667
11490
|
"provider_error",
|
|
10668
11491
|
"skill_activated",
|
|
10669
|
-
"skill_deactivated"
|
|
10670
|
-
|
|
10671
|
-
|
|
10672
|
-
|
|
11492
|
+
"skill_deactivated",
|
|
11493
|
+
// Resume markers and fleet lifecycle. These reach disk today only because
|
|
11494
|
+
// every producer appends to the raw SessionWriter and bypasses this bridge —
|
|
11495
|
+
// routed through it while absent from both sets, `isAllowed` would drop them
|
|
11496
|
+
// at the DEFAULT level and the resume timeline would lose them silently.
|
|
11497
|
+
"mode_changed",
|
|
11498
|
+
"agent_spawned",
|
|
11499
|
+
"agent_stopped",
|
|
11500
|
+
"agent_error",
|
|
11501
|
+
"task_created",
|
|
11502
|
+
"task_updated",
|
|
11503
|
+
"task_completed",
|
|
11504
|
+
"task_failed",
|
|
11505
|
+
"side_effect"
|
|
10673
11506
|
]);
|
|
11507
|
+
var FULL_ONLY_EVENTS = /* @__PURE__ */ new Set(["tool_progress"]);
|
|
10674
11508
|
function isAllowed(type, level) {
|
|
10675
11509
|
if (CORE_RECONSTRUCT_EVENTS.has(type)) return true;
|
|
10676
11510
|
if (level === "minimal") return false;
|
|
@@ -10725,9 +11559,7 @@ function createSessionEventBridge(writer, level = "standard", options = {}) {
|
|
|
10725
11559
|
async appendBatch(events) {
|
|
10726
11560
|
const target = resolveWriter();
|
|
10727
11561
|
if (!target || events.length === 0) return;
|
|
10728
|
-
const allowed = events.filter(
|
|
10729
|
-
(e) => isAllowed(e.type, currentLevel) && shouldSample(e)
|
|
10730
|
-
);
|
|
11562
|
+
const allowed = events.filter((e) => isAllowed(e.type, currentLevel) && shouldSample(e));
|
|
10731
11563
|
if (allowed.length === 0) return;
|
|
10732
11564
|
try {
|
|
10733
11565
|
await target.appendBatch(allowed);
|
|
@@ -10792,9 +11624,12 @@ export {
|
|
|
10792
11624
|
ToolAuditLog,
|
|
10793
11625
|
addPlanItem,
|
|
10794
11626
|
appendJournal,
|
|
11627
|
+
applyGoalDeliverableCompletions,
|
|
11628
|
+
applyRewindToConversation,
|
|
10795
11629
|
attachPlanCheckpoint,
|
|
10796
11630
|
attachTodosCheckpoint,
|
|
10797
11631
|
clearPlan,
|
|
11632
|
+
coordinateGoalIteration,
|
|
10798
11633
|
createGoalKanbanBoard,
|
|
10799
11634
|
createSessionEventBridge,
|
|
10800
11635
|
deleteGoalKanbanBoard,
|
|
@@ -10815,6 +11650,7 @@ export {
|
|
|
10815
11650
|
getSessionRegistry,
|
|
10816
11651
|
goalFilePath,
|
|
10817
11652
|
hasSessionRegistry,
|
|
11653
|
+
isGoalDeliverableComplete,
|
|
10818
11654
|
listPlanTemplates,
|
|
10819
11655
|
loadCompletedWorkCheckpoint,
|
|
10820
11656
|
loadDirectorState,
|
|
@@ -10826,9 +11662,12 @@ export {
|
|
|
10826
11662
|
mutatePlan,
|
|
10827
11663
|
mutateTasks,
|
|
10828
11664
|
parseAutonomyChoice,
|
|
11665
|
+
parseCompletedGoalDeliverables,
|
|
10829
11666
|
parseEntries,
|
|
10830
11667
|
parseProgressFromText,
|
|
10831
11668
|
promptChecksum,
|
|
11669
|
+
readProviderSnapshot,
|
|
11670
|
+
recomputeGoalProgress,
|
|
10832
11671
|
recordProgress,
|
|
10833
11672
|
removePlanItem,
|
|
10834
11673
|
resolveAuditLevel,
|
|
@@ -10842,6 +11681,7 @@ export {
|
|
|
10842
11681
|
saveTodosCheckpoint,
|
|
10843
11682
|
setPlanItemStatus,
|
|
10844
11683
|
setProgress,
|
|
11684
|
+
stripGoalDeliverableMarker,
|
|
10845
11685
|
summarizeUsage,
|
|
10846
11686
|
watchProviderConfig
|
|
10847
11687
|
};
|