@renseiai/agentfactory 0.8.7 → 0.8.9
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/src/config/index.d.ts +1 -1
- package/dist/src/config/index.d.ts.map +1 -1
- package/dist/src/config/index.js +1 -1
- package/dist/src/config/repository-config.d.ts +37 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +47 -0
- package/dist/src/config/repository-config.test.js +140 -1
- package/dist/src/governor/decision-engine.d.ts +3 -0
- package/dist/src/governor/decision-engine.d.ts.map +1 -1
- package/dist/src/governor/decision-engine.js +11 -0
- package/dist/src/governor/decision-engine.test.js +33 -0
- package/dist/src/governor/event-types.d.ts +18 -1
- package/dist/src/governor/event-types.d.ts.map +1 -1
- package/dist/src/governor/event-types.js +4 -0
- package/dist/src/governor/governor-types.d.ts +1 -1
- package/dist/src/governor/governor-types.d.ts.map +1 -1
- package/dist/src/governor/governor.d.ts +17 -1
- package/dist/src/governor/governor.d.ts.map +1 -1
- package/dist/src/governor/governor.js +112 -1
- package/dist/src/governor/governor.test.js +155 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
- package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.js +243 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
- package/dist/src/merge-queue/index.d.ts +18 -0
- package/dist/src/merge-queue/index.d.ts.map +1 -0
- package/dist/src/merge-queue/index.js +28 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
- package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
- package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
- package/dist/src/merge-queue/types.d.ts +48 -0
- package/dist/src/merge-queue/types.d.ts.map +1 -0
- package/dist/src/merge-queue/types.js +8 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
- package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.js +235 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
- package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
- package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
- package/dist/src/orchestrator/context-manager.d.ts +72 -0
- package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.js +120 -0
- package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
- package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
- package/dist/src/orchestrator/context-manager.test.js +137 -0
- package/dist/src/orchestrator/index.d.ts +8 -2
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +8 -1
- package/dist/src/orchestrator/issue-tracker-client.d.ts +4 -0
- package/dist/src/orchestrator/issue-tracker-client.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.d.ts +12 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +282 -2
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +6 -0
- package/dist/src/orchestrator/parse-work-result.test.js +19 -0
- package/dist/src/orchestrator/state-recovery.d.ts +21 -2
- package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
- package/dist/src/orchestrator/state-recovery.js +54 -2
- package/dist/src/orchestrator/state-recovery.test.js +106 -2
- package/dist/src/orchestrator/state-types.d.ts +62 -0
- package/dist/src/orchestrator/state-types.d.ts.map +1 -1
- package/dist/src/orchestrator/state-types.js +5 -1
- package/dist/src/orchestrator/summary-builder.d.ts +47 -0
- package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.js +240 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
- package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
- package/dist/src/orchestrator/summary-builder.test.js +236 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/orchestrator/work-types.d.ts +1 -1
- package/dist/src/orchestrator/work-types.d.ts.map +1 -1
- package/dist/src/providers/index.d.ts +64 -1
- package/dist/src/providers/index.d.ts.map +1 -1
- package/dist/src/providers/index.js +132 -1
- package/dist/src/providers/index.test.js +340 -2
- package/dist/src/routing/index.d.ts +7 -0
- package/dist/src/routing/index.d.ts.map +1 -0
- package/dist/src/routing/index.js +6 -0
- package/dist/src/routing/observation-recorder.d.ts +19 -0
- package/dist/src/routing/observation-recorder.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.js +73 -0
- package/dist/src/routing/observation-recorder.test.d.ts +2 -0
- package/dist/src/routing/observation-recorder.test.d.ts.map +1 -0
- package/dist/src/routing/observation-recorder.test.js +322 -0
- package/dist/src/routing/observation-store.d.ts +40 -0
- package/dist/src/routing/observation-store.d.ts.map +1 -0
- package/dist/src/routing/observation-store.js +1 -0
- package/dist/src/routing/observation-store.test.d.ts +2 -0
- package/dist/src/routing/observation-store.test.d.ts.map +1 -0
- package/dist/src/routing/observation-store.test.js +138 -0
- package/dist/src/routing/posterior-store.d.ts +12 -0
- package/dist/src/routing/posterior-store.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.js +13 -0
- package/dist/src/routing/posterior-store.test.d.ts +2 -0
- package/dist/src/routing/posterior-store.test.d.ts.map +1 -0
- package/dist/src/routing/posterior-store.test.js +37 -0
- package/dist/src/routing/reward.d.ts +16 -0
- package/dist/src/routing/reward.d.ts.map +1 -0
- package/dist/src/routing/reward.js +29 -0
- package/dist/src/routing/reward.test.d.ts +2 -0
- package/dist/src/routing/reward.test.d.ts.map +1 -0
- package/dist/src/routing/reward.test.js +210 -0
- package/dist/src/routing/routing-engine.d.ts +20 -0
- package/dist/src/routing/routing-engine.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.js +113 -0
- package/dist/src/routing/routing-engine.test.d.ts +2 -0
- package/dist/src/routing/routing-engine.test.d.ts.map +1 -0
- package/dist/src/routing/routing-engine.test.js +310 -0
- package/dist/src/routing/types.d.ts +157 -0
- package/dist/src/routing/types.d.ts.map +1 -0
- package/dist/src/routing/types.js +68 -0
- package/dist/src/routing/types.test.d.ts +2 -0
- package/dist/src/routing/types.test.d.ts.map +1 -0
- package/dist/src/routing/types.test.js +184 -0
- package/dist/src/templates/registry.test.js +2 -2
- package/dist/src/templates/types.d.ts +5 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +3 -0
- package/dist/src/workflow/agent-cancellation.d.ts +37 -0
- package/dist/src/workflow/agent-cancellation.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.js +41 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts +2 -0
- package/dist/src/workflow/agent-cancellation.test.d.ts.map +1 -0
- package/dist/src/workflow/agent-cancellation.test.js +86 -0
- package/dist/src/workflow/branching-router.d.ts +38 -0
- package/dist/src/workflow/branching-router.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.js +52 -0
- package/dist/src/workflow/branching-router.test.d.ts +2 -0
- package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
- package/dist/src/workflow/branching-router.test.js +209 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts +21 -0
- package/dist/src/workflow/concurrency-semaphore.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.js +46 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts +2 -0
- package/dist/src/workflow/concurrency-semaphore.test.d.ts.map +1 -0
- package/dist/src/workflow/concurrency-semaphore.test.js +183 -0
- package/dist/src/workflow/duration.d.ts +28 -0
- package/dist/src/workflow/duration.d.ts.map +1 -0
- package/dist/src/workflow/duration.js +57 -0
- package/dist/src/workflow/duration.test.d.ts +2 -0
- package/dist/src/workflow/duration.test.d.ts.map +1 -0
- package/dist/src/workflow/duration.test.js +74 -0
- package/dist/src/workflow/expression/ast.d.ts +53 -0
- package/dist/src/workflow/expression/ast.d.ts.map +1 -0
- package/dist/src/workflow/expression/ast.js +8 -0
- package/dist/src/workflow/expression/context.d.ts +40 -0
- package/dist/src/workflow/expression/context.d.ts.map +1 -0
- package/dist/src/workflow/expression/context.js +37 -0
- package/dist/src/workflow/expression/evaluator.d.ts +28 -0
- package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.js +165 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
- package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/evaluator.test.js +792 -0
- package/dist/src/workflow/expression/expression.test.d.ts +2 -0
- package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
- package/dist/src/workflow/expression/expression.test.js +516 -0
- package/dist/src/workflow/expression/helpers.d.ts +21 -0
- package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
- package/dist/src/workflow/expression/helpers.js +56 -0
- package/dist/src/workflow/expression/index.d.ts +55 -0
- package/dist/src/workflow/expression/index.d.ts.map +1 -0
- package/dist/src/workflow/expression/index.js +71 -0
- package/dist/src/workflow/expression/lexer.d.ts +37 -0
- package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
- package/dist/src/workflow/expression/lexer.js +166 -0
- package/dist/src/workflow/expression/parser.d.ts +23 -0
- package/dist/src/workflow/expression/parser.d.ts.map +1 -0
- package/dist/src/workflow/expression/parser.js +181 -0
- package/dist/src/workflow/gate-state.d.ts +115 -0
- package/dist/src/workflow/gate-state.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.js +185 -0
- package/dist/src/workflow/gate-state.test.d.ts +2 -0
- package/dist/src/workflow/gate-state.test.d.ts.map +1 -0
- package/dist/src/workflow/gate-state.test.js +251 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts +119 -0
- package/dist/src/workflow/gates/gate-evaluator.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.js +243 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts +2 -0
- package/dist/src/workflow/gates/gate-evaluator.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/gate-evaluator.test.js +240 -0
- package/dist/src/workflow/gates/signal-gate.d.ts +114 -0
- package/dist/src/workflow/gates/signal-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.js +216 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/signal-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/signal-gate.test.js +199 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts +96 -0
- package/dist/src/workflow/gates/timeout-engine.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.js +162 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts +2 -0
- package/dist/src/workflow/gates/timeout-engine.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timeout-engine.test.js +186 -0
- package/dist/src/workflow/gates/timer-gate.d.ts +125 -0
- package/dist/src/workflow/gates/timer-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.js +381 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/timer-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/timer-gate.test.js +211 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts +132 -0
- package/dist/src/workflow/gates/webhook-gate.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.js +216 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts +2 -0
- package/dist/src/workflow/gates/webhook-gate.test.d.ts.map +1 -0
- package/dist/src/workflow/gates/webhook-gate.test.js +182 -0
- package/dist/src/workflow/index.d.ts +31 -3
- package/dist/src/workflow/index.d.ts.map +1 -1
- package/dist/src/workflow/index.js +20 -1
- package/dist/src/workflow/parallelism-executor.d.ts +25 -0
- package/dist/src/workflow/parallelism-executor.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.js +53 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts +2 -0
- package/dist/src/workflow/parallelism-executor.test.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-executor.test.js +191 -0
- package/dist/src/workflow/parallelism-types.d.ts +80 -0
- package/dist/src/workflow/parallelism-types.d.ts.map +1 -0
- package/dist/src/workflow/parallelism-types.js +8 -0
- package/dist/src/workflow/phase-context-injector.d.ts +29 -0
- package/dist/src/workflow/phase-context-injector.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.js +43 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts +2 -0
- package/dist/src/workflow/phase-context-injector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-context-injector.test.js +123 -0
- package/dist/src/workflow/phase-output-collector.d.ts +39 -0
- package/dist/src/workflow/phase-output-collector.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.js +141 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts +2 -0
- package/dist/src/workflow/phase-output-collector.test.d.ts.map +1 -0
- package/dist/src/workflow/phase-output-collector.test.js +179 -0
- package/dist/src/workflow/retry-resolver.d.ts +51 -0
- package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.js +70 -0
- package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
- package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
- package/dist/src/workflow/retry-resolver.test.js +149 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts +21 -0
- package/dist/src/workflow/strategies/fan-in-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.js +92 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-in-strategy.test.js +182 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts +16 -0
- package/dist/src/workflow/strategies/fan-out-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.js +47 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/fan-out-strategy.test.js +97 -0
- package/dist/src/workflow/strategies/index.d.ts +4 -0
- package/dist/src/workflow/strategies/index.d.ts.map +1 -0
- package/dist/src/workflow/strategies/index.js +3 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts +19 -0
- package/dist/src/workflow/strategies/race-strategy.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.js +92 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts +2 -0
- package/dist/src/workflow/strategies/race-strategy.test.d.ts.map +1 -0
- package/dist/src/workflow/strategies/race-strategy.test.js +318 -0
- package/dist/src/workflow/transition-engine.d.ts +3 -1
- package/dist/src/workflow/transition-engine.d.ts.map +1 -1
- package/dist/src/workflow/transition-engine.js +26 -7
- package/dist/src/workflow/transition-engine.test.js +215 -11
- package/dist/src/workflow/workflow-registry.d.ts +46 -1
- package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
- package/dist/src/workflow/workflow-registry.js +74 -0
- package/dist/src/workflow/workflow-registry.test.js +54 -0
- package/dist/src/workflow/workflow-types.d.ts +330 -12
- package/dist/src/workflow/workflow-types.d.ts.map +1 -1
- package/dist/src/workflow/workflow-types.js +100 -5
- package/dist/src/workflow/workflow-types.test.js +293 -2
- package/package.json +2 -2
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { AgentEvent } from '../providers/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Extended file action type for the artifact tracker.
|
|
4
|
+
* Includes 'searched' beyond what StructuredSummary uses.
|
|
5
|
+
*/
|
|
6
|
+
export type TrackedFileAction = 'read' | 'created' | 'modified' | 'deleted' | 'searched';
|
|
7
|
+
/**
|
|
8
|
+
* A tracked file with its operation history
|
|
9
|
+
*/
|
|
10
|
+
export interface TrackedFile {
|
|
11
|
+
/** Absolute file path */
|
|
12
|
+
path: string;
|
|
13
|
+
/** Path relative to worktree root */
|
|
14
|
+
relativePath: string;
|
|
15
|
+
/** Chronological list of operations performed */
|
|
16
|
+
actions: TrackedFileAction[];
|
|
17
|
+
/** Unix timestamp when file was first encountered */
|
|
18
|
+
firstSeenAt: number;
|
|
19
|
+
/** Unix timestamp of most recent operation */
|
|
20
|
+
lastTouchedAt: number;
|
|
21
|
+
/** Number of lines read (from Read tool) */
|
|
22
|
+
linesRead?: number;
|
|
23
|
+
/** Number of lines modified (from Edit/Write tools) */
|
|
24
|
+
linesModified?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The full artifact index
|
|
28
|
+
*/
|
|
29
|
+
export interface ArtifactIndex {
|
|
30
|
+
/** Map of absolute path to tracked file info */
|
|
31
|
+
files: Record<string, TrackedFile>;
|
|
32
|
+
/** Total read operations performed */
|
|
33
|
+
totalReads: number;
|
|
34
|
+
/** Total write operations performed */
|
|
35
|
+
totalWrites: number;
|
|
36
|
+
/** Unix timestamp of last index update */
|
|
37
|
+
lastUpdatedAt: number;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Tracks files read/modified during an agent session.
|
|
41
|
+
* Parses tool_use events to extract file operations and maintains
|
|
42
|
+
* a persistent index for context window management.
|
|
43
|
+
*/
|
|
44
|
+
export declare class ArtifactTracker {
|
|
45
|
+
private index;
|
|
46
|
+
private worktreeRoot;
|
|
47
|
+
constructor(worktreeRoot: string, existingIndex?: ArtifactIndex);
|
|
48
|
+
/**
|
|
49
|
+
* Process an agent event to extract file operations.
|
|
50
|
+
* Only processes tool_use events (not tool_result).
|
|
51
|
+
*/
|
|
52
|
+
trackEvent(event: AgentEvent): void;
|
|
53
|
+
/**
|
|
54
|
+
* Process a tool_use event to extract file operations
|
|
55
|
+
*/
|
|
56
|
+
private trackToolUseEvent;
|
|
57
|
+
/**
|
|
58
|
+
* Parse bash commands for file operation heuristics
|
|
59
|
+
*/
|
|
60
|
+
private parseBashCommand;
|
|
61
|
+
/**
|
|
62
|
+
* Record a file action in the index
|
|
63
|
+
*/
|
|
64
|
+
private recordAction;
|
|
65
|
+
/**
|
|
66
|
+
* Get the current artifact index
|
|
67
|
+
*/
|
|
68
|
+
getIndex(): ArtifactIndex;
|
|
69
|
+
/**
|
|
70
|
+
* Get files matching optional filters
|
|
71
|
+
*/
|
|
72
|
+
getFiles(filter?: {
|
|
73
|
+
action?: TrackedFileAction;
|
|
74
|
+
glob?: string;
|
|
75
|
+
}): TrackedFile[];
|
|
76
|
+
/**
|
|
77
|
+
* Persist the index to .agent/artifacts.json atomically
|
|
78
|
+
*/
|
|
79
|
+
persist(): void;
|
|
80
|
+
/**
|
|
81
|
+
* Load a persisted artifact index from .agent/artifacts.json
|
|
82
|
+
*/
|
|
83
|
+
static load(worktreeRoot: string): ArtifactTracker;
|
|
84
|
+
/**
|
|
85
|
+
* Generate a compact summary string for context injection
|
|
86
|
+
*/
|
|
87
|
+
toContextString(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Summarize action counts for a file
|
|
90
|
+
*/
|
|
91
|
+
private summarizeActions;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=artifact-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-tracker.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/artifact-tracker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAG1E;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;AAExF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAA;IACrB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAClC,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAA;CACtB;AAYD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,YAAY,CAAQ;gBAEhB,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,aAAa;IAU/D;;;OAGG;IACH,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAKnC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkDzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,QAAQ,IAAI,aAAa;IAIzB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,EAAE;IAyB/E;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe;IAoBlD;;OAEG;IACH,eAAe,IAAI,MAAM;IAqCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CASzB"}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
2
|
+
import { resolve, relative } from 'path';
|
|
3
|
+
import { initializeAgentDir, getAgentDir } from './state-recovery.js';
|
|
4
|
+
/**
|
|
5
|
+
* Tracks files read/modified during an agent session.
|
|
6
|
+
* Parses tool_use events to extract file operations and maintains
|
|
7
|
+
* a persistent index for context window management.
|
|
8
|
+
*/
|
|
9
|
+
export class ArtifactTracker {
|
|
10
|
+
index;
|
|
11
|
+
worktreeRoot;
|
|
12
|
+
constructor(worktreeRoot, existingIndex) {
|
|
13
|
+
this.worktreeRoot = worktreeRoot;
|
|
14
|
+
this.index = existingIndex ?? {
|
|
15
|
+
files: {},
|
|
16
|
+
totalReads: 0,
|
|
17
|
+
totalWrites: 0,
|
|
18
|
+
lastUpdatedAt: Date.now(),
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Process an agent event to extract file operations.
|
|
23
|
+
* Only processes tool_use events (not tool_result).
|
|
24
|
+
*/
|
|
25
|
+
trackEvent(event) {
|
|
26
|
+
if (event.type !== 'tool_use')
|
|
27
|
+
return;
|
|
28
|
+
this.trackToolUseEvent(event);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Process a tool_use event to extract file operations
|
|
32
|
+
*/
|
|
33
|
+
trackToolUseEvent(event) {
|
|
34
|
+
const { toolName, input } = event;
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
switch (toolName) {
|
|
37
|
+
case 'Read': {
|
|
38
|
+
const filePath = input.file_path;
|
|
39
|
+
if (filePath) {
|
|
40
|
+
this.recordAction(filePath, 'read', now);
|
|
41
|
+
this.index.totalReads++;
|
|
42
|
+
}
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case 'Write': {
|
|
46
|
+
const filePath = input.file_path;
|
|
47
|
+
if (filePath) {
|
|
48
|
+
// Check if file exists to determine created vs modified
|
|
49
|
+
const action = existsSync(filePath) ? 'modified' : 'created';
|
|
50
|
+
this.recordAction(filePath, action, now);
|
|
51
|
+
this.index.totalWrites++;
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'Edit': {
|
|
56
|
+
const filePath = input.file_path;
|
|
57
|
+
if (filePath) {
|
|
58
|
+
this.recordAction(filePath, 'modified', now);
|
|
59
|
+
this.index.totalWrites++;
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case 'Glob': {
|
|
64
|
+
// Glob results are in tool_result, not tool_use; we track the search pattern
|
|
65
|
+
// We don't track individual files from glob since results come in tool_result
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
case 'Grep': {
|
|
69
|
+
// Similar to Glob — results come in tool_result
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'Bash': {
|
|
73
|
+
const command = input.command;
|
|
74
|
+
if (command) {
|
|
75
|
+
this.parseBashCommand(command, now);
|
|
76
|
+
}
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Parse bash commands for file operation heuristics
|
|
83
|
+
*/
|
|
84
|
+
parseBashCommand(command, timestamp) {
|
|
85
|
+
// Detect rm commands
|
|
86
|
+
const rmMatch = command.match(/\brm\s+(?:-[rRfv]+\s+)*([^\s|;&]+)/);
|
|
87
|
+
if (rmMatch && rmMatch[1]) {
|
|
88
|
+
this.recordAction(rmMatch[1], 'deleted', timestamp);
|
|
89
|
+
this.index.totalWrites++;
|
|
90
|
+
}
|
|
91
|
+
// Detect mv commands (source deleted, dest created)
|
|
92
|
+
const mvMatch = command.match(/\bmv\s+(?:-[fv]+\s+)*([^\s|;&]+)\s+([^\s|;&]+)/);
|
|
93
|
+
if (mvMatch && mvMatch[1] && mvMatch[2]) {
|
|
94
|
+
this.recordAction(mvMatch[1], 'deleted', timestamp);
|
|
95
|
+
this.recordAction(mvMatch[2], 'created', timestamp);
|
|
96
|
+
this.index.totalWrites += 2;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Record a file action in the index
|
|
101
|
+
*/
|
|
102
|
+
recordAction(filePath, action, timestamp) {
|
|
103
|
+
const absPath = resolve(this.worktreeRoot, filePath);
|
|
104
|
+
const relPath = relative(this.worktreeRoot, absPath);
|
|
105
|
+
const existing = this.index.files[absPath];
|
|
106
|
+
if (existing) {
|
|
107
|
+
existing.actions.push(action);
|
|
108
|
+
existing.lastTouchedAt = timestamp;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.index.files[absPath] = {
|
|
112
|
+
path: absPath,
|
|
113
|
+
relativePath: relPath,
|
|
114
|
+
actions: [action],
|
|
115
|
+
firstSeenAt: timestamp,
|
|
116
|
+
lastTouchedAt: timestamp,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
this.index.lastUpdatedAt = timestamp;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Get the current artifact index
|
|
123
|
+
*/
|
|
124
|
+
getIndex() {
|
|
125
|
+
return this.index;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Get files matching optional filters
|
|
129
|
+
*/
|
|
130
|
+
getFiles(filter) {
|
|
131
|
+
let files = Object.values(this.index.files);
|
|
132
|
+
if (filter?.action) {
|
|
133
|
+
files = files.filter(f => f.actions.includes(filter.action));
|
|
134
|
+
}
|
|
135
|
+
if (filter?.glob) {
|
|
136
|
+
const pattern = filter.glob;
|
|
137
|
+
files = files.filter(f => {
|
|
138
|
+
// Simple glob matching: * matches anything except /, ** matches anything
|
|
139
|
+
const regex = new RegExp('^' + pattern
|
|
140
|
+
.replace(/\*\*/g, '___DOUBLESTAR___')
|
|
141
|
+
.replace(/\*/g, '[^/]*')
|
|
142
|
+
.replace(/___DOUBLESTAR___/g, '.*')
|
|
143
|
+
.replace(/\?/g, '[^/]') + '$');
|
|
144
|
+
return regex.test(f.relativePath);
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return files;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Persist the index to .agent/artifacts.json atomically
|
|
151
|
+
*/
|
|
152
|
+
persist() {
|
|
153
|
+
const agentDir = getAgentDir(this.worktreeRoot);
|
|
154
|
+
initializeAgentDir(this.worktreeRoot);
|
|
155
|
+
const artifactsPath = resolve(agentDir, 'artifacts.json');
|
|
156
|
+
const tempPath = artifactsPath + '.tmp';
|
|
157
|
+
const serialized = {
|
|
158
|
+
files: this.index.files,
|
|
159
|
+
totalReads: this.index.totalReads,
|
|
160
|
+
totalWrites: this.index.totalWrites,
|
|
161
|
+
lastUpdatedAt: this.index.lastUpdatedAt,
|
|
162
|
+
};
|
|
163
|
+
writeFileSync(tempPath, JSON.stringify(serialized, null, 2));
|
|
164
|
+
renameSync(tempPath, artifactsPath);
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Load a persisted artifact index from .agent/artifacts.json
|
|
168
|
+
*/
|
|
169
|
+
static load(worktreeRoot) {
|
|
170
|
+
const artifactsPath = resolve(getAgentDir(worktreeRoot), 'artifacts.json');
|
|
171
|
+
try {
|
|
172
|
+
if (!existsSync(artifactsPath)) {
|
|
173
|
+
return new ArtifactTracker(worktreeRoot);
|
|
174
|
+
}
|
|
175
|
+
const content = readFileSync(artifactsPath, 'utf-8');
|
|
176
|
+
const data = JSON.parse(content);
|
|
177
|
+
const index = {
|
|
178
|
+
files: data.files ?? {},
|
|
179
|
+
totalReads: data.totalReads ?? 0,
|
|
180
|
+
totalWrites: data.totalWrites ?? 0,
|
|
181
|
+
lastUpdatedAt: data.lastUpdatedAt ?? Date.now(),
|
|
182
|
+
};
|
|
183
|
+
return new ArtifactTracker(worktreeRoot, index);
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return new ArtifactTracker(worktreeRoot);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Generate a compact summary string for context injection
|
|
191
|
+
*/
|
|
192
|
+
toContextString() {
|
|
193
|
+
const files = Object.values(this.index.files);
|
|
194
|
+
if (files.length === 0)
|
|
195
|
+
return '## Files Tracked (0 files)\nNo files tracked yet.';
|
|
196
|
+
const modified = files.filter(f => f.actions.some(a => a === 'created' || a === 'modified' || a === 'deleted'));
|
|
197
|
+
const readOnly = files.filter(f => !f.actions.some(a => a === 'created' || a === 'modified' || a === 'deleted') && f.actions.includes('read'));
|
|
198
|
+
const searchedOnly = files.filter(f => f.actions.every(a => a === 'searched'));
|
|
199
|
+
const lines = [];
|
|
200
|
+
lines.push(`## Files Tracked (${files.length} files)`);
|
|
201
|
+
if (modified.length > 0) {
|
|
202
|
+
lines.push(`### Modified (${modified.length}):`);
|
|
203
|
+
for (const f of modified) {
|
|
204
|
+
const actionCounts = this.summarizeActions(f.actions);
|
|
205
|
+
lines.push(`- ${f.relativePath} (${actionCounts})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (readOnly.length > 0) {
|
|
209
|
+
lines.push(`### Read-only (${readOnly.length}):`);
|
|
210
|
+
for (const f of readOnly) {
|
|
211
|
+
const readCount = f.actions.filter(a => a === 'read').length;
|
|
212
|
+
lines.push(`- ${f.relativePath} (read ${readCount}x)`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (searchedOnly.length > 0) {
|
|
216
|
+
lines.push(`### Searched (${searchedOnly.length}):`);
|
|
217
|
+
for (const f of searchedOnly) {
|
|
218
|
+
lines.push(`- ${f.relativePath}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return lines.join('\n');
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Summarize action counts for a file
|
|
225
|
+
*/
|
|
226
|
+
summarizeActions(actions) {
|
|
227
|
+
const counts = {};
|
|
228
|
+
for (const action of actions) {
|
|
229
|
+
counts[action] = (counts[action] ?? 0) + 1;
|
|
230
|
+
}
|
|
231
|
+
return Object.entries(counts)
|
|
232
|
+
.map(([action, count]) => `${action} ${count}x`)
|
|
233
|
+
.join(', ');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"artifact-tracker.test.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/artifact-tracker.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
vi.mock('fs', () => ({
|
|
3
|
+
existsSync: vi.fn(),
|
|
4
|
+
readFileSync: vi.fn(),
|
|
5
|
+
writeFileSync: vi.fn(),
|
|
6
|
+
renameSync: vi.fn(),
|
|
7
|
+
mkdirSync: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
import { ArtifactTracker } from './artifact-tracker.js';
|
|
12
|
+
const WORKTREE = '/tmp/test-worktree';
|
|
13
|
+
function makeToolUse(toolName, input) {
|
|
14
|
+
return {
|
|
15
|
+
type: 'tool_use',
|
|
16
|
+
toolName,
|
|
17
|
+
input,
|
|
18
|
+
raw: {},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
vi.clearAllMocks();
|
|
23
|
+
// By default, existsSync returns true (for file existence checks in Write tool)
|
|
24
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
25
|
+
});
|
|
26
|
+
describe('ArtifactTracker — trackEvent', () => {
|
|
27
|
+
it('tracks Read tool events', () => {
|
|
28
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
29
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
30
|
+
const index = tracker.getIndex();
|
|
31
|
+
expect(Object.keys(index.files)).toHaveLength(1);
|
|
32
|
+
expect(index.totalReads).toBe(1);
|
|
33
|
+
const file = Object.values(index.files)[0];
|
|
34
|
+
expect(file.relativePath).toBe('src/foo.ts');
|
|
35
|
+
expect(file.actions).toEqual(['read']);
|
|
36
|
+
});
|
|
37
|
+
it('tracks Write tool events as modified when file exists', () => {
|
|
38
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
39
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
40
|
+
tracker.trackEvent(makeToolUse('Write', { file_path: '/tmp/test-worktree/src/bar.ts' }));
|
|
41
|
+
const index = tracker.getIndex();
|
|
42
|
+
expect(index.totalWrites).toBe(1);
|
|
43
|
+
const file = Object.values(index.files)[0];
|
|
44
|
+
expect(file.actions).toEqual(['modified']);
|
|
45
|
+
});
|
|
46
|
+
it('tracks Write tool events as created when file does not exist', () => {
|
|
47
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
48
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
49
|
+
tracker.trackEvent(makeToolUse('Write', { file_path: '/tmp/test-worktree/src/new.ts' }));
|
|
50
|
+
const file = Object.values(tracker.getIndex().files)[0];
|
|
51
|
+
expect(file.actions).toEqual(['created']);
|
|
52
|
+
});
|
|
53
|
+
it('tracks Edit tool events as modified', () => {
|
|
54
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
55
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/edit.ts' }));
|
|
56
|
+
const file = Object.values(tracker.getIndex().files)[0];
|
|
57
|
+
expect(file.actions).toEqual(['modified']);
|
|
58
|
+
expect(tracker.getIndex().totalWrites).toBe(1);
|
|
59
|
+
});
|
|
60
|
+
it('accumulates actions for the same file', () => {
|
|
61
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
62
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
63
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
64
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
65
|
+
const file = Object.values(tracker.getIndex().files)[0];
|
|
66
|
+
expect(file.actions).toEqual(['read', 'modified', 'read']);
|
|
67
|
+
expect(tracker.getIndex().totalReads).toBe(2);
|
|
68
|
+
expect(tracker.getIndex().totalWrites).toBe(1);
|
|
69
|
+
});
|
|
70
|
+
it('ignores non-tool_use events', () => {
|
|
71
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
72
|
+
tracker.trackEvent({ type: 'assistant_text', text: 'hello', raw: {} });
|
|
73
|
+
tracker.trackEvent({ type: 'system', subtype: 'status', raw: {} });
|
|
74
|
+
expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
it('parses bash rm commands', () => {
|
|
77
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
78
|
+
tracker.trackEvent(makeToolUse('Bash', { command: 'rm -f /tmp/test-worktree/src/old.ts' }));
|
|
79
|
+
const files = Object.values(tracker.getIndex().files);
|
|
80
|
+
expect(files).toHaveLength(1);
|
|
81
|
+
expect(files[0].actions).toEqual(['deleted']);
|
|
82
|
+
});
|
|
83
|
+
it('parses bash mv commands', () => {
|
|
84
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
85
|
+
tracker.trackEvent(makeToolUse('Bash', { command: 'mv /tmp/test-worktree/src/a.ts /tmp/test-worktree/src/b.ts' }));
|
|
86
|
+
const files = Object.values(tracker.getIndex().files);
|
|
87
|
+
expect(files).toHaveLength(2);
|
|
88
|
+
});
|
|
89
|
+
it('ignores tool_use events with missing file_path', () => {
|
|
90
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
91
|
+
tracker.trackEvent(makeToolUse('Read', {}));
|
|
92
|
+
tracker.trackEvent(makeToolUse('Write', {}));
|
|
93
|
+
tracker.trackEvent(makeToolUse('Edit', {}));
|
|
94
|
+
expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('ArtifactTracker — getFiles', () => {
|
|
98
|
+
it('returns all files when no filter', () => {
|
|
99
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
100
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/a.ts' }));
|
|
101
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/b.ts' }));
|
|
102
|
+
expect(tracker.getFiles()).toHaveLength(2);
|
|
103
|
+
});
|
|
104
|
+
it('filters by action', () => {
|
|
105
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
106
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/a.ts' }));
|
|
107
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/b.ts' }));
|
|
108
|
+
expect(tracker.getFiles({ action: 'read' })).toHaveLength(1);
|
|
109
|
+
expect(tracker.getFiles({ action: 'modified' })).toHaveLength(1);
|
|
110
|
+
});
|
|
111
|
+
it('filters by glob pattern', () => {
|
|
112
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
113
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
114
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/test/bar.ts' }));
|
|
115
|
+
const srcFiles = tracker.getFiles({ glob: 'src/**' });
|
|
116
|
+
expect(srcFiles).toHaveLength(1);
|
|
117
|
+
expect(srcFiles[0].relativePath).toBe('src/foo.ts');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe('ArtifactTracker — persistence', () => {
|
|
121
|
+
it('persists index atomically', () => {
|
|
122
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
123
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
124
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
125
|
+
tracker.persist();
|
|
126
|
+
const artifactsPath = resolve(WORKTREE, '.agent', 'artifacts.json');
|
|
127
|
+
expect(writeFileSync).toHaveBeenCalledWith(artifactsPath + '.tmp', expect.any(String));
|
|
128
|
+
expect(renameSync).toHaveBeenCalledWith(artifactsPath + '.tmp', artifactsPath);
|
|
129
|
+
});
|
|
130
|
+
it('loads persisted index', () => {
|
|
131
|
+
const savedIndex = {
|
|
132
|
+
files: {
|
|
133
|
+
'/tmp/test-worktree/src/foo.ts': {
|
|
134
|
+
path: '/tmp/test-worktree/src/foo.ts',
|
|
135
|
+
relativePath: 'src/foo.ts',
|
|
136
|
+
actions: ['read', 'modified'],
|
|
137
|
+
firstSeenAt: 1000,
|
|
138
|
+
lastTouchedAt: 2000,
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
totalReads: 1,
|
|
142
|
+
totalWrites: 1,
|
|
143
|
+
lastUpdatedAt: 2000,
|
|
144
|
+
};
|
|
145
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
146
|
+
vi.mocked(readFileSync).mockReturnValue(JSON.stringify(savedIndex));
|
|
147
|
+
const tracker = ArtifactTracker.load(WORKTREE);
|
|
148
|
+
const index = tracker.getIndex();
|
|
149
|
+
expect(Object.keys(index.files)).toHaveLength(1);
|
|
150
|
+
expect(index.totalReads).toBe(1);
|
|
151
|
+
expect(index.totalWrites).toBe(1);
|
|
152
|
+
});
|
|
153
|
+
it('returns empty tracker when no persisted file exists', () => {
|
|
154
|
+
vi.mocked(existsSync).mockReturnValue(false);
|
|
155
|
+
const tracker = ArtifactTracker.load(WORKTREE);
|
|
156
|
+
expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
|
|
157
|
+
});
|
|
158
|
+
it('returns empty tracker when persisted file is invalid JSON', () => {
|
|
159
|
+
vi.mocked(existsSync).mockReturnValue(true);
|
|
160
|
+
vi.mocked(readFileSync).mockReturnValue('not json');
|
|
161
|
+
const tracker = ArtifactTracker.load(WORKTREE);
|
|
162
|
+
expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
describe('ArtifactTracker — toContextString', () => {
|
|
166
|
+
it('returns empty message when no files tracked', () => {
|
|
167
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
168
|
+
expect(tracker.toContextString()).toContain('0 files');
|
|
169
|
+
});
|
|
170
|
+
it('categorizes files into modified, read-only, and searched', () => {
|
|
171
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
172
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/modified.ts' }));
|
|
173
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/readonly.ts' }));
|
|
174
|
+
const output = tracker.toContextString();
|
|
175
|
+
expect(output).toContain('Modified (1)');
|
|
176
|
+
expect(output).toContain('Read-only (1)');
|
|
177
|
+
expect(output).toContain('src/modified.ts');
|
|
178
|
+
expect(output).toContain('src/readonly.ts');
|
|
179
|
+
});
|
|
180
|
+
it('shows action counts for modified files', () => {
|
|
181
|
+
const tracker = new ArtifactTracker(WORKTREE);
|
|
182
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
183
|
+
tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
184
|
+
tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
|
|
185
|
+
const output = tracker.toContextString();
|
|
186
|
+
expect(output).toContain('read 2x');
|
|
187
|
+
expect(output).toContain('modified 1x');
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { AgentEvent } from '../providers/types.js';
|
|
2
|
+
import type { StructuredSummary } from './state-types.js';
|
|
3
|
+
import type { ArtifactIndex } from './artifact-tracker.js';
|
|
4
|
+
/**
|
|
5
|
+
* Configuration for the ContextManager
|
|
6
|
+
*/
|
|
7
|
+
export interface ContextManagerConfig {
|
|
8
|
+
/** Root path of the worktree for file resolution and persistence */
|
|
9
|
+
worktreeRoot: string;
|
|
10
|
+
/** Maximum events to buffer between compaction boundaries */
|
|
11
|
+
maxEventBufferSize?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Coordinates ArtifactTracker, SummaryBuilder, and StructuredSummary
|
|
15
|
+
* for context window management during an agent session.
|
|
16
|
+
*
|
|
17
|
+
* Processes all agent events, tracks file operations, and handles
|
|
18
|
+
* compaction boundaries by generating incremental summaries.
|
|
19
|
+
*/
|
|
20
|
+
export declare class ContextManager {
|
|
21
|
+
private artifactTracker;
|
|
22
|
+
private summaryBuilder;
|
|
23
|
+
private currentSummary;
|
|
24
|
+
private eventBuffer;
|
|
25
|
+
private worktreeRoot;
|
|
26
|
+
private maxEventBufferSize;
|
|
27
|
+
private lastPersistAt;
|
|
28
|
+
constructor(config: ContextManagerConfig);
|
|
29
|
+
/**
|
|
30
|
+
* Process an agent event. Called for every event in the stream.
|
|
31
|
+
* Feeds tool events to ArtifactTracker and buffers events for compaction.
|
|
32
|
+
*/
|
|
33
|
+
processEvent(event: AgentEvent): void;
|
|
34
|
+
/**
|
|
35
|
+
* Handle a compaction boundary event from the provider.
|
|
36
|
+
* Generates an incremental summary from buffered events and merges
|
|
37
|
+
* it with the existing persisted summary.
|
|
38
|
+
*/
|
|
39
|
+
handleCompaction(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Get the current context injection string for the agent prompt.
|
|
42
|
+
* Returns empty string if no summary exists yet.
|
|
43
|
+
*/
|
|
44
|
+
getContextSection(): string;
|
|
45
|
+
/**
|
|
46
|
+
* Get the current structured summary (if any)
|
|
47
|
+
*/
|
|
48
|
+
getSummary(): StructuredSummary | null;
|
|
49
|
+
/**
|
|
50
|
+
* Get the current artifact index
|
|
51
|
+
*/
|
|
52
|
+
getArtifactIndex(): ArtifactIndex;
|
|
53
|
+
/**
|
|
54
|
+
* Persist all state (summary + artifacts) to disk.
|
|
55
|
+
* Uses atomic writes to survive crashes.
|
|
56
|
+
*/
|
|
57
|
+
persist(): void;
|
|
58
|
+
/**
|
|
59
|
+
* Persist if enough time has passed since last persist (debounced).
|
|
60
|
+
* Call this periodically (e.g., on every tool event) to ensure
|
|
61
|
+
* state is saved without excessive I/O.
|
|
62
|
+
*
|
|
63
|
+
* @param intervalMs - Minimum interval between persists (default: 30000ms)
|
|
64
|
+
*/
|
|
65
|
+
persistIfStale(intervalMs?: number): void;
|
|
66
|
+
/**
|
|
67
|
+
* Load a ContextManager from persisted state (for session resume).
|
|
68
|
+
* Loads both summary.json and artifacts.json from the .agent/ directory.
|
|
69
|
+
*/
|
|
70
|
+
static load(worktreeRoot: string): ContextManager;
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=context-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-manager.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/context-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAI1D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,MAAM,EAAE,oBAAoB;IAUxC;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAkBrC;;;;OAIG;IACH,gBAAgB,IAAI,IAAI;IAyBxB;;;OAGG;IACH,iBAAiB,IAAI,MAAM;IAQ3B;;OAEG;IACH,UAAU,IAAI,iBAAiB,GAAG,IAAI;IAItC;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;;OAGG;IACH,OAAO,IAAI,IAAI;IAQf;;;;;;OAMG;IACH,cAAc,CAAC,UAAU,GAAE,MAAc,GAAG,IAAI;IAOhD;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc;CAMlD"}
|