ccjk 13.3.5 → 13.3.7
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/chunks/agent-teams.mjs +7 -5
- package/dist/chunks/agent.mjs +2 -2
- package/dist/chunks/agents.mjs +16 -16
- package/dist/chunks/api-cli.mjs +6 -6
- package/dist/chunks/api-providers.mjs +1 -1
- package/dist/chunks/api.mjs +4 -4
- package/dist/chunks/auto-bootstrap.mjs +1 -1
- package/dist/chunks/auto-fix.mjs +49 -4
- package/dist/chunks/auto-fixer.mjs +7 -5
- package/dist/chunks/auto-init.mjs +9 -7208
- package/dist/chunks/auto-memory-bridge.mjs +9 -3
- package/dist/chunks/auto-updater.mjs +9 -9
- package/dist/chunks/auto-upgrade.mjs +5 -3
- package/dist/chunks/banner.mjs +4 -3
- package/dist/chunks/boost.mjs +118 -62
- package/dist/chunks/ccjk-agents.mjs +3 -3
- package/dist/chunks/ccjk-all.mjs +7 -7
- package/dist/chunks/ccjk-config.mjs +2 -2
- package/dist/chunks/ccjk-hooks.mjs +4 -4
- package/dist/chunks/ccjk-mcp.mjs +5 -5
- package/dist/chunks/ccjk-setup.mjs +5 -5
- package/dist/chunks/ccjk-skills.mjs +5 -5
- package/dist/chunks/ccr.mjs +18 -16
- package/dist/chunks/ccu.mjs +2 -2
- package/dist/chunks/check-updates.mjs +8 -8
- package/dist/chunks/claude-code-config-manager.mjs +11 -8
- package/dist/chunks/claude-code-incremental-manager.mjs +7 -7
- package/dist/chunks/claude-config.mjs +1 -1
- package/dist/chunks/claude-wrapper.mjs +1 -1
- package/dist/chunks/cli-hook.mjs +15 -15
- package/dist/chunks/codex-config-switch.mjs +7 -7
- package/dist/chunks/codex-provider-manager.mjs +7 -7
- package/dist/chunks/codex-uninstaller.mjs +2 -2
- package/dist/chunks/codex.mjs +5 -5
- package/dist/chunks/commands.mjs +2 -2
- package/dist/chunks/commands2.mjs +3 -3
- package/dist/chunks/commit.mjs +2 -2
- package/dist/chunks/completion.mjs +2 -2
- package/dist/chunks/config-consolidator.mjs +2 -2
- package/dist/chunks/config-switch.mjs +8 -8
- package/dist/chunks/config.mjs +6 -5
- package/dist/chunks/config2.mjs +5 -5
- package/dist/chunks/config3.mjs +4 -4
- package/dist/chunks/constants.mjs +1 -1
- package/dist/chunks/context-opt.mjs +92 -90
- package/dist/chunks/context.mjs +659 -0
- package/dist/chunks/dashboard.mjs +14 -9
- package/dist/chunks/doctor.mjs +4 -4
- package/dist/chunks/eval.mjs +502 -0
- package/dist/chunks/evolution.mjs +46 -39
- package/dist/chunks/health-alerts.mjs +9 -9
- package/dist/chunks/help.mjs +1 -1
- package/dist/chunks/hook-installer.mjs +6 -3
- package/dist/chunks/index.mjs +23 -0
- package/dist/chunks/index10.mjs +634 -571
- package/dist/chunks/index11.mjs +1061 -569
- package/dist/chunks/index12.mjs +914 -1076
- package/dist/chunks/index13.mjs +136 -951
- package/dist/chunks/index14.mjs +209 -185
- package/dist/chunks/index2.mjs +19 -24
- package/dist/chunks/index3.mjs +19085 -12
- package/dist/chunks/index4.mjs +16 -19092
- package/dist/chunks/index5.mjs +7602 -16
- package/dist/chunks/index6.mjs +159 -7590
- package/dist/chunks/index7.mjs +1602 -171
- package/dist/chunks/index8.mjs +19 -1602
- package/dist/chunks/index9.mjs +612 -15
- package/dist/chunks/init.mjs +26 -19
- package/dist/chunks/installer.mjs +5 -5
- package/dist/chunks/installer2.mjs +2 -2
- package/dist/chunks/intent-engine.mjs +1 -1
- package/dist/chunks/interview.mjs +4 -4
- package/dist/chunks/manager.mjs +1 -1
- package/dist/chunks/marketplace.mjs +2 -2
- package/dist/chunks/mcp-cli.mjs +12 -12
- package/dist/chunks/mcp.mjs +8 -8
- package/dist/chunks/memory.mjs +8 -8
- package/dist/chunks/menu-hierarchical.mjs +24 -22
- package/dist/chunks/menu.mjs +27 -22
- package/dist/chunks/metrics-display.mjs +2 -2
- package/dist/chunks/migrator.mjs +1 -1
- package/dist/chunks/monitor.mjs +2 -2
- package/dist/chunks/notification.mjs +6 -6
- package/dist/chunks/onboarding-wizard.mjs +6 -5
- package/dist/chunks/onboarding.mjs +4 -4
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/paradigm.mjs +2 -2
- package/dist/chunks/permission-manager.mjs +2 -2
- package/dist/chunks/permissions.mjs +3 -3
- package/dist/chunks/persistence-manager.mjs +19 -12
- package/dist/chunks/persistence.mjs +5 -3
- package/dist/chunks/plugin.mjs +2 -2
- package/dist/chunks/prompts.mjs +5 -5
- package/dist/chunks/providers.mjs +2 -2
- package/dist/chunks/quick-actions.mjs +7 -6
- package/dist/chunks/quick-provider.mjs +5 -4
- package/dist/chunks/quick-setup.mjs +20 -15
- package/dist/chunks/remote.mjs +15 -16
- package/dist/chunks/{convoy-manager.mjs → session-manager.mjs} +1129 -1095
- package/dist/chunks/session.mjs +2 -2
- package/dist/chunks/sessions.mjs +3 -3
- package/dist/chunks/silent-updater.mjs +1 -1
- package/dist/chunks/simple-config.mjs +2 -2
- package/dist/chunks/skill2.mjs +3 -3
- package/dist/chunks/skills-sync.mjs +5 -5
- package/dist/chunks/skills.mjs +3 -3
- package/dist/chunks/slash-commands.mjs +9 -8
- package/dist/chunks/smart-defaults.mjs +9 -5
- package/dist/chunks/startup.mjs +1 -1
- package/dist/chunks/stats.mjs +2 -2
- package/dist/chunks/status.mjs +37 -22
- package/dist/chunks/team.mjs +3 -3
- package/dist/chunks/thinking.mjs +4 -4
- package/dist/chunks/trace.mjs +2 -2
- package/dist/chunks/uninstall.mjs +9 -9
- package/dist/chunks/update.mjs +14 -11
- package/dist/chunks/upgrade-manager.mjs +3 -3
- package/dist/chunks/upgrade.mjs +25 -9
- package/dist/chunks/version-checker.mjs +4 -4
- package/dist/chunks/vim.mjs +3 -3
- package/dist/chunks/workflows.mjs +1 -1
- package/dist/chunks/wsl.mjs +1 -1
- package/dist/chunks/zero-config.mjs +4 -4
- package/dist/cli.mjs +60 -26
- package/dist/index.d.mts +4392 -4392
- package/dist/index.d.ts +4392 -4392
- package/dist/index.mjs +4314 -4314
- package/dist/shared/{ccjk.DcKLglJQ.mjs → ccjk.BIxuVL3_.mjs} +2 -2
- package/dist/shared/{ccjk.DJdmgr2d.mjs → ccjk.BJMRY2Ra.mjs} +5 -3
- package/dist/shared/{ccjk.B1TwPltj.mjs → ccjk.BOu1yav7.mjs} +3 -2
- package/dist/shared/{ccjk.mJpVRDZ8.mjs → ccjk.BWFpnOr3.mjs} +1 -1
- package/dist/shared/{ccjk.BfIpomdz.mjs → ccjk.CHUEFqmw.mjs} +3 -2
- package/dist/shared/{ccjk.CqdbaXqU.mjs → ccjk.CLUL0pAV.mjs} +9 -5
- package/dist/shared/{ccjk.Cot9p9_n.mjs → ccjk.Cjj8SVrn.mjs} +1 -1
- package/dist/shared/{ccjk.CfrpIIKy.mjs → ccjk.Crd_nEfj.mjs} +38 -20
- package/dist/shared/{ccjk.DCw2WnZU.mjs → ccjk.CvChMYvB.mjs} +1 -1
- package/dist/shared/{ccjk.CXzjn01x.mjs → ccjk.D8ZLYSZZ.mjs} +1 -1
- package/dist/shared/{ccjk.BrPUmTqm.mjs → ccjk.DJuyfrlL.mjs} +164 -82
- package/dist/shared/{ccjk.DHXfsrwn.mjs → ccjk.DRfdq6yl.mjs} +4 -4
- package/dist/shared/{ccjk.DXRAZcix.mjs → ccjk.DScm_NnL.mjs} +8 -4
- package/dist/shared/{ccjk.XsJWJuQP.mjs → ccjk.DfZKjHvG.mjs} +6 -128
- package/dist/shared/{ccjk.BFxsJM0k.mjs → ccjk.DwSebGy0.mjs} +4 -3
- package/dist/shared/ccjk.DxWqH-EF.mjs +170 -0
- package/dist/shared/{ccjk.Cwa_FiTX.mjs → ccjk.I6IuYdc_.mjs} +2 -2
- package/dist/shared/{ccjk.DpstNaeR.mjs → ccjk.KpFl2RDA.mjs} +3 -3
- package/dist/shared/{ccjk.dYDLfmph.mjs → ccjk._dESH4Rk.mjs} +1 -1
- package/dist/shared/{ccjk.BxSmJ8B7.mjs → ccjk.wLJHO0Af.mjs} +2 -1
- package/package.json +65 -67
- package/dist/chunks/index15.mjs +0 -218
- package/dist/shared/{ccjk.c-ETfBZ_.mjs → ccjk.eIn-g1yI.mjs} +96 -96
|
@@ -1,829 +1,810 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { n as nanoid } from '../shared/ccjk.BoApaI4j.mjs';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { x as K } from './main.mjs';
|
|
5
6
|
import { j as join } from '../shared/ccjk.bQ7Dh1g4.mjs';
|
|
7
|
+
import { glob } from 'tinyglobby';
|
|
8
|
+
import { e as executionTracer } from '../shared/ccjk.CHUEFqmw.mjs';
|
|
9
|
+
import { f as fsParadigm } from '../shared/ccjk.wLJHO0Af.mjs';
|
|
6
10
|
import 'node:child_process';
|
|
7
11
|
import 'node:fs/promises';
|
|
8
|
-
import { homedir } from 'node:os';
|
|
9
12
|
import 'node:path';
|
|
10
|
-
import { EventEmitter } from 'node:events';
|
|
11
|
-
import { n as nanoid } from '../shared/ccjk.BoApaI4j.mjs';
|
|
12
|
-
import { x as K } from './main.mjs';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
const CHARS_PER_TOKEN = 4;
|
|
30
|
-
function estimateTokens(text) {
|
|
31
|
-
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
32
|
-
}
|
|
33
|
-
function truncateToDepth(content, depth) {
|
|
34
|
-
if (depth === "L2") return content;
|
|
35
|
-
const maxChars = DEPTH_TOKEN_BUDGETS[depth] * CHARS_PER_TOKEN;
|
|
36
|
-
if (content.length <= maxChars) return content;
|
|
37
|
-
if (depth === "L0") {
|
|
38
|
-
const firstPara = content.split(/\n\n/)[0];
|
|
39
|
-
return firstPara.length <= maxChars ? firstPara : content.slice(0, maxChars);
|
|
14
|
+
class GitBackedStateManager extends EventEmitter {
|
|
15
|
+
config;
|
|
16
|
+
worktrees = /* @__PURE__ */ new Map();
|
|
17
|
+
initialized = false;
|
|
18
|
+
pendingCommits = /* @__PURE__ */ new Map();
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
super();
|
|
21
|
+
this.config = {
|
|
22
|
+
workspaceRoot: config.workspaceRoot ?? join(homedir(), ".ccjk", "workspace"),
|
|
23
|
+
useWorktrees: config.useWorktrees ?? true,
|
|
24
|
+
autoCommit: config.autoCommit ?? true,
|
|
25
|
+
compressionLevel: config.compressionLevel ?? 6,
|
|
26
|
+
verbose: config.verbose ?? false,
|
|
27
|
+
commitBatchInterval: config.commitBatchInterval ?? 1e3
|
|
28
|
+
};
|
|
40
29
|
}
|
|
41
|
-
return `${content.slice(0, maxChars)}
|
|
42
|
-
... (truncated, use L2 for full content)`;
|
|
43
|
-
}
|
|
44
|
-
class ContextLoader {
|
|
45
|
-
cache = /* @__PURE__ */ new Map();
|
|
46
|
-
defaultMaxSize = 1e5;
|
|
47
|
-
// 100KB default
|
|
48
|
-
defaultTokenBudget = 8e3;
|
|
49
|
-
// ~32KB at 4 chars/token
|
|
50
30
|
/**
|
|
51
|
-
*
|
|
52
|
-
* If depth is explicit, use it. Otherwise derive from tokenBudget.
|
|
31
|
+
* Initialize the workspace with Git
|
|
53
32
|
*/
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
33
|
+
async initialize() {
|
|
34
|
+
if (this.initialized)
|
|
35
|
+
return;
|
|
36
|
+
const { workspaceRoot } = this.config;
|
|
37
|
+
try {
|
|
38
|
+
if (!existsSync(workspaceRoot)) {
|
|
39
|
+
mkdirSync(workspaceRoot, { recursive: true });
|
|
40
|
+
this.log(`Created workspace: ${workspaceRoot}`);
|
|
41
|
+
}
|
|
42
|
+
const gitDir = join(workspaceRoot, ".git");
|
|
43
|
+
if (!existsSync(gitDir)) {
|
|
44
|
+
await this.exec("git", ["init"], workspaceRoot);
|
|
45
|
+
this.log("Initialized Git repository");
|
|
46
|
+
const gitignore = `
|
|
47
|
+
# Sensitive data
|
|
48
|
+
*.env
|
|
49
|
+
*.key
|
|
50
|
+
*.pem
|
|
51
|
+
credentials.json
|
|
52
|
+
|
|
53
|
+
# Temporary files
|
|
54
|
+
*.tmp
|
|
55
|
+
*.log
|
|
56
|
+
*.swp
|
|
57
|
+
|
|
58
|
+
# OS files
|
|
59
|
+
.DS_Store
|
|
60
|
+
Thumbs.db
|
|
61
|
+
`;
|
|
62
|
+
writeFileSync(join(workspaceRoot, ".gitignore"), gitignore.trim());
|
|
63
|
+
await this.exec("git", ["add", ".gitignore"], workspaceRoot);
|
|
64
|
+
await this.exec("git", ["commit", "-m", "Initial commit"], workspaceRoot);
|
|
65
|
+
this.log("Created initial commit");
|
|
66
|
+
}
|
|
67
|
+
const agentsDir = join(workspaceRoot, "agents");
|
|
68
|
+
if (!existsSync(agentsDir)) {
|
|
69
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
70
|
+
}
|
|
71
|
+
await this.loadExistingWorktrees();
|
|
72
|
+
this.initialized = true;
|
|
73
|
+
this.emit("initialized");
|
|
74
|
+
this.log("GitBackedStateManager initialized");
|
|
75
|
+
} catch (error) {
|
|
76
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
77
|
+
this.emit("error", err);
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
59
80
|
}
|
|
60
81
|
/**
|
|
61
|
-
*
|
|
62
|
-
* Pass tokenBudget to automatically select L0/L1/L2 depth.
|
|
82
|
+
* Create isolated worktree for agent
|
|
63
83
|
*/
|
|
64
|
-
async
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
depth: explicitDepth,
|
|
70
|
-
layers = ["project", "domain", "task", "execution"],
|
|
71
|
-
task
|
|
72
|
-
} = options;
|
|
73
|
-
const phaseMultiplier = options.taskPhase ? PHASE_BUDGET_MULTIPLIERS[options.taskPhase] ?? 1 : 1;
|
|
74
|
-
const effectiveBudget = Math.round((tokenBudget ?? this.defaultTokenBudget) * phaseMultiplier);
|
|
75
|
-
const depth = this.resolveDepth(effectiveBudget, explicitDepth);
|
|
76
|
-
const cacheKey = this.getCacheKey(projectRoot, layers, task?.id, depth);
|
|
77
|
-
const cached = this.cache.get(cacheKey);
|
|
78
|
-
if (cached && Date.now() - cached.loadedAt < 6e4) {
|
|
79
|
-
executionTracer.logEvent("context-load", {
|
|
80
|
-
source: "cache",
|
|
81
|
-
layers: layers.length,
|
|
82
|
-
size: cached.totalSize,
|
|
83
|
-
tokens: cached.totalTokens,
|
|
84
|
-
depth
|
|
85
|
-
});
|
|
86
|
-
return cached;
|
|
84
|
+
async createAgentWorktree(agentId) {
|
|
85
|
+
await this.ensureInitialized();
|
|
86
|
+
const { workspaceRoot, useWorktrees } = this.config;
|
|
87
|
+
if (this.worktrees.has(agentId)) {
|
|
88
|
+
return this.worktrees.get(agentId);
|
|
87
89
|
}
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
const agentPath = join(workspaceRoot, "agents", agentId);
|
|
91
|
+
if (useWorktrees) {
|
|
92
|
+
const branchName = `agent/${agentId}`;
|
|
93
|
+
try {
|
|
94
|
+
await this.exec("git", ["checkout", "--orphan", branchName], workspaceRoot);
|
|
95
|
+
await this.exec("git", ["reset", "--hard"], workspaceRoot);
|
|
96
|
+
await this.exec("git", ["checkout", "main"], workspaceRoot).catch(() => {
|
|
97
|
+
return this.exec("git", ["checkout", "master"], workspaceRoot);
|
|
98
|
+
});
|
|
99
|
+
if (!existsSync(agentPath)) {
|
|
100
|
+
await this.exec("git", ["worktree", "add", agentPath, branchName], workspaceRoot);
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
if (!existsSync(agentPath)) {
|
|
104
|
+
mkdirSync(agentPath, { recursive: true });
|
|
105
|
+
}
|
|
101
106
|
}
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
} else {
|
|
108
|
+
if (!existsSync(agentPath)) {
|
|
109
|
+
mkdirSync(agentPath, { recursive: true });
|
|
104
110
|
}
|
|
105
111
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
112
|
+
const stateFile = join(agentPath, "state.json");
|
|
113
|
+
const mailboxFile = join(agentPath, "mailbox.json");
|
|
114
|
+
if (!existsSync(stateFile)) {
|
|
115
|
+
writeFileSync(stateFile, JSON.stringify({ agentId, createdAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
116
|
+
}
|
|
117
|
+
if (!existsSync(mailboxFile)) {
|
|
118
|
+
writeFileSync(mailboxFile, JSON.stringify({ inbox: [], outbox: [], archive: [] }, null, 2));
|
|
119
|
+
}
|
|
120
|
+
this.worktrees.set(agentId, agentPath);
|
|
121
|
+
this.emit("worktree:created", agentId, agentPath);
|
|
122
|
+
this.log(`Created worktree for agent: ${agentId}`);
|
|
123
|
+
return agentPath;
|
|
115
124
|
}
|
|
116
125
|
/**
|
|
117
|
-
*
|
|
126
|
+
* Save agent state to Git
|
|
118
127
|
*/
|
|
119
|
-
async
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
default:
|
|
130
|
-
return [];
|
|
128
|
+
async saveState(agentId, state) {
|
|
129
|
+
await this.ensureInitialized();
|
|
130
|
+
const agentPath = await this.getOrCreateAgentPath(agentId);
|
|
131
|
+
const stateFile = join(agentPath, "state.json");
|
|
132
|
+
let existingState = {};
|
|
133
|
+
if (existsSync(stateFile)) {
|
|
134
|
+
try {
|
|
135
|
+
existingState = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
136
|
+
} catch {
|
|
137
|
+
}
|
|
131
138
|
}
|
|
139
|
+
const newState = {
|
|
140
|
+
...existingState,
|
|
141
|
+
...state,
|
|
142
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
143
|
+
};
|
|
144
|
+
writeFileSync(stateFile, JSON.stringify(newState, null, 2));
|
|
145
|
+
let commitId = "";
|
|
146
|
+
if (this.config.autoCommit) {
|
|
147
|
+
commitId = await this.batchCommit(agentId, `Update state: ${agentId}`);
|
|
148
|
+
}
|
|
149
|
+
this.emit("state:saved", agentId, commitId);
|
|
150
|
+
this.log(`Saved state for agent: ${agentId}`);
|
|
151
|
+
return commitId;
|
|
132
152
|
}
|
|
133
153
|
/**
|
|
134
|
-
* Load
|
|
135
|
-
* - README.md
|
|
136
|
-
* - CLAUDE.md
|
|
137
|
-
* - package.json
|
|
138
|
-
* - tsconfig.json
|
|
154
|
+
* Load agent state from Git
|
|
139
155
|
*/
|
|
140
|
-
async
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
156
|
+
async loadState(agentId) {
|
|
157
|
+
await this.ensureInitialized();
|
|
158
|
+
const agentPath = this.worktrees.get(agentId);
|
|
159
|
+
if (!agentPath) {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
const stateFile = join(agentPath, "state.json");
|
|
163
|
+
if (!existsSync(stateFile)) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
try {
|
|
167
|
+
const state = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
168
|
+
this.emit("state:loaded", agentId);
|
|
169
|
+
this.log(`Loaded state for agent: ${agentId}`);
|
|
170
|
+
return state;
|
|
171
|
+
} catch {
|
|
172
|
+
return null;
|
|
158
173
|
}
|
|
159
|
-
return entries;
|
|
160
174
|
}
|
|
161
175
|
/**
|
|
162
|
-
*
|
|
163
|
-
* Based on task type/domain, load relevant files
|
|
176
|
+
* Rollback to previous state
|
|
164
177
|
*/
|
|
165
|
-
async
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
async rollback(agentId, commitId) {
|
|
179
|
+
await this.ensureInitialized();
|
|
180
|
+
const agentPath = this.worktrees.get(agentId);
|
|
181
|
+
if (!agentPath) {
|
|
182
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
await this.exec("git", ["reset", "--hard", commitId], agentPath);
|
|
186
|
+
this.emit("state:rolled-back", agentId, commitId);
|
|
187
|
+
this.log(`Rolled back agent ${agentId} to commit ${commitId}`);
|
|
188
|
+
} catch (error) {
|
|
189
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
190
|
+
this.emit("error", err);
|
|
191
|
+
throw err;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Get state history for agent
|
|
196
|
+
*/
|
|
197
|
+
async getHistory(agentId, limit = 10) {
|
|
198
|
+
await this.ensureInitialized();
|
|
199
|
+
const agentPath = this.worktrees.get(agentId);
|
|
200
|
+
if (!agentPath) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
const result = await this.exec(
|
|
205
|
+
"git",
|
|
206
|
+
["log", `--max-count=${limit}`, "--format=%H|%s|%ct", "--", "state.json"],
|
|
207
|
+
agentPath
|
|
208
|
+
);
|
|
209
|
+
const snapshots = [];
|
|
210
|
+
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
211
|
+
const [commitId, message, timestamp] = line.split("|");
|
|
212
|
+
const stateResult = await this.exec(
|
|
213
|
+
"git",
|
|
214
|
+
["show", `${commitId}:state.json`],
|
|
215
|
+
agentPath
|
|
216
|
+
).catch(() => ({ stdout: "{}" }));
|
|
217
|
+
let state;
|
|
173
218
|
try {
|
|
174
|
-
|
|
175
|
-
const content = truncateToDepth(raw, depth);
|
|
176
|
-
entries.push({
|
|
177
|
-
layer: "domain",
|
|
178
|
-
source: file.replace(projectRoot, ""),
|
|
179
|
-
content,
|
|
180
|
-
priority: 75,
|
|
181
|
-
depth,
|
|
182
|
-
tokenEstimate: estimateTokens(content),
|
|
183
|
-
metadata: { paradigm: structure.paradigm, path: file }
|
|
184
|
-
});
|
|
219
|
+
state = JSON.parse(stateResult.stdout);
|
|
185
220
|
} catch {
|
|
221
|
+
state = {};
|
|
186
222
|
}
|
|
223
|
+
snapshots.push({
|
|
224
|
+
id: nanoid(),
|
|
225
|
+
agentId,
|
|
226
|
+
commitId,
|
|
227
|
+
timestamp: Number.parseInt(timestamp, 10) * 1e3,
|
|
228
|
+
message,
|
|
229
|
+
state
|
|
230
|
+
});
|
|
187
231
|
}
|
|
188
|
-
return
|
|
232
|
+
return snapshots;
|
|
233
|
+
} catch {
|
|
234
|
+
return [];
|
|
189
235
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
source: file.replace(projectRoot, ""),
|
|
204
|
-
content,
|
|
205
|
-
priority: 80,
|
|
206
|
-
depth,
|
|
207
|
-
tokenEstimate: estimateTokens(content),
|
|
208
|
-
metadata: { domain, paradigm: structure.paradigm, path: file }
|
|
209
|
-
});
|
|
210
|
-
} catch {
|
|
211
|
-
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Cleanup agent worktree
|
|
239
|
+
*/
|
|
240
|
+
async cleanup(agentId) {
|
|
241
|
+
await this.ensureInitialized();
|
|
242
|
+
const agentPath = this.worktrees.get(agentId);
|
|
243
|
+
if (!agentPath) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
if (this.config.useWorktrees) {
|
|
248
|
+
await this.exec("git", ["worktree", "remove", agentPath, "--force"], this.config.workspaceRoot);
|
|
212
249
|
}
|
|
250
|
+
this.worktrees.delete(agentId);
|
|
251
|
+
this.emit("worktree:removed", agentId);
|
|
252
|
+
this.log(`Cleaned up worktree for agent: ${agentId}`);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
255
|
+
this.emit("error", err);
|
|
256
|
+
throw err;
|
|
213
257
|
}
|
|
214
|
-
return entries;
|
|
215
258
|
}
|
|
216
259
|
/**
|
|
217
|
-
*
|
|
260
|
+
* Get all agent IDs
|
|
218
261
|
*/
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const entries = [];
|
|
222
|
-
if (task.description) {
|
|
223
|
-
const content = truncateToDepth(task.description, depth);
|
|
224
|
-
entries.push({
|
|
225
|
-
layer: "task",
|
|
226
|
-
source: "task-description",
|
|
227
|
-
content,
|
|
228
|
-
priority: 90,
|
|
229
|
-
depth,
|
|
230
|
-
tokenEstimate: estimateTokens(content),
|
|
231
|
-
metadata: { taskId: task.id }
|
|
232
|
-
});
|
|
233
|
-
}
|
|
234
|
-
if (task.input && depth !== "L0") {
|
|
235
|
-
const content = truncateToDepth(JSON.stringify(task.input, null, 2), depth);
|
|
236
|
-
entries.push({
|
|
237
|
-
layer: "task",
|
|
238
|
-
source: "task-input",
|
|
239
|
-
content,
|
|
240
|
-
priority: 85,
|
|
241
|
-
depth,
|
|
242
|
-
tokenEstimate: estimateTokens(content),
|
|
243
|
-
metadata: { taskId: task.id }
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
return entries;
|
|
262
|
+
getAgentIds() {
|
|
263
|
+
return Array.from(this.worktrees.keys());
|
|
247
264
|
}
|
|
248
265
|
/**
|
|
249
|
-
*
|
|
250
|
-
* - Recent errors
|
|
251
|
-
* - Previous attempts
|
|
252
|
-
* - Related task outputs
|
|
266
|
+
* Check if agent exists
|
|
253
267
|
*/
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const entries = [];
|
|
257
|
-
if (task.output && depth !== "L0") {
|
|
258
|
-
const content = truncateToDepth(JSON.stringify(task.output, null, 2), depth);
|
|
259
|
-
entries.push({
|
|
260
|
-
layer: "execution",
|
|
261
|
-
source: "previous-output",
|
|
262
|
-
content,
|
|
263
|
-
priority: 70,
|
|
264
|
-
depth,
|
|
265
|
-
tokenEstimate: estimateTokens(content),
|
|
266
|
-
metadata: { taskId: task.id }
|
|
267
|
-
});
|
|
268
|
-
}
|
|
269
|
-
return entries;
|
|
268
|
+
hasAgent(agentId) {
|
|
269
|
+
return this.worktrees.has(agentId);
|
|
270
270
|
}
|
|
271
271
|
/**
|
|
272
|
-
*
|
|
272
|
+
* Get workspace root path
|
|
273
273
|
*/
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
const domains = {
|
|
277
|
-
api: ["api", "endpoint", "route", "controller"],
|
|
278
|
-
ui: ["ui", "component", "view", "page", "frontend"],
|
|
279
|
-
database: ["database", "db", "schema", "migration", "model"],
|
|
280
|
-
auth: ["auth", "login", "user", "permission", "session"],
|
|
281
|
-
test: ["test", "spec", "e2e", "unit", "integration"],
|
|
282
|
-
config: ["config", "settings", "env", "setup"]
|
|
283
|
-
};
|
|
284
|
-
for (const [domain, keywords] of Object.entries(domains)) {
|
|
285
|
-
if (keywords.some((kw) => text.includes(kw))) {
|
|
286
|
-
return domain;
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
return null;
|
|
274
|
+
getWorkspaceRoot() {
|
|
275
|
+
return this.config.workspaceRoot;
|
|
290
276
|
}
|
|
291
277
|
/**
|
|
292
|
-
* Get
|
|
278
|
+
* Get agent path
|
|
293
279
|
*/
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
280
|
+
getAgentPath(agentId) {
|
|
281
|
+
return this.worktrees.get(agentId);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Get state history for agent
|
|
285
|
+
*/
|
|
286
|
+
async getStateHistory(agentId, limit = 20) {
|
|
287
|
+
await this.ensureInitialized();
|
|
288
|
+
const agentPath = this.worktrees.get(agentId);
|
|
289
|
+
if (!agentPath) {
|
|
290
|
+
return { agentId, commits: [], totalCommits: 0 };
|
|
291
|
+
}
|
|
292
|
+
try {
|
|
293
|
+
const result = await this.exec(
|
|
294
|
+
"git",
|
|
295
|
+
["log", `--max-count=${limit}`, "--format=%H|%s|%ct", "--", "state.json"],
|
|
296
|
+
agentPath
|
|
297
|
+
);
|
|
298
|
+
const commits = result.stdout.split("\n").filter(Boolean).map((line) => {
|
|
299
|
+
const [id, message, timestamp] = line.split("|");
|
|
300
|
+
return {
|
|
301
|
+
id,
|
|
302
|
+
message,
|
|
303
|
+
timestamp: Number.parseInt(timestamp, 10) * 1e3
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
const countResult = await this.exec(
|
|
307
|
+
"git",
|
|
308
|
+
["rev-list", "--count", "HEAD", "--", "state.json"],
|
|
309
|
+
agentPath
|
|
310
|
+
).catch(() => ({ stdout: "0" }));
|
|
311
|
+
return {
|
|
312
|
+
agentId,
|
|
313
|
+
commits,
|
|
314
|
+
totalCommits: Number.parseInt(countResult.stdout.trim(), 10) || commits.length
|
|
315
|
+
};
|
|
316
|
+
} catch {
|
|
317
|
+
return { agentId, commits: [], totalCommits: 0 };
|
|
312
318
|
}
|
|
313
|
-
return patterns;
|
|
314
319
|
}
|
|
315
320
|
/**
|
|
316
|
-
*
|
|
321
|
+
* Create a named snapshot of current state
|
|
317
322
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
323
|
+
async createSnapshot(agentId, description) {
|
|
324
|
+
await this.ensureInitialized();
|
|
325
|
+
const agentPath = this.worktrees.get(agentId);
|
|
326
|
+
if (!agentPath) {
|
|
327
|
+
throw new Error(`Agent not found: ${agentId}`);
|
|
328
|
+
}
|
|
329
|
+
const commitId = await this.commit(agentId, `Snapshot: ${description}`);
|
|
330
|
+
if (!commitId) {
|
|
331
|
+
const result = await this.exec("git", ["rev-parse", "HEAD"], agentPath);
|
|
332
|
+
const currentCommitId = result.stdout.trim();
|
|
333
|
+
const state2 = await this.loadState(agentId);
|
|
334
|
+
return {
|
|
335
|
+
id: nanoid(),
|
|
336
|
+
agentId,
|
|
337
|
+
commitId: currentCommitId,
|
|
338
|
+
timestamp: Date.now(),
|
|
339
|
+
message: description,
|
|
340
|
+
state: state2 ?? {}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const state = await this.loadState(agentId);
|
|
344
|
+
return {
|
|
345
|
+
id: nanoid(),
|
|
346
|
+
agentId,
|
|
347
|
+
commitId,
|
|
348
|
+
timestamp: Date.now(),
|
|
349
|
+
message: description,
|
|
350
|
+
state: state ?? {}
|
|
351
|
+
};
|
|
320
352
|
}
|
|
321
353
|
/**
|
|
322
|
-
*
|
|
354
|
+
* Restore state from a snapshot
|
|
323
355
|
*/
|
|
324
|
-
|
|
325
|
-
this.
|
|
356
|
+
async restoreSnapshot(agentId, snapshotId) {
|
|
357
|
+
await this.rollback(agentId, snapshotId);
|
|
326
358
|
}
|
|
327
359
|
/**
|
|
328
|
-
*
|
|
360
|
+
* Sync with remote (if configured)
|
|
329
361
|
*/
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (!entries || entries.length === 0) continue;
|
|
338
|
-
sections.push(`# ${layer.toUpperCase()} CONTEXT
|
|
339
|
-
`);
|
|
340
|
-
const sorted = entries.sort((a, b) => b.priority - a.priority);
|
|
341
|
-
for (const entry of sorted) {
|
|
342
|
-
sections.push(`## ${entry.source} [${entry.depth}]
|
|
343
|
-
`);
|
|
344
|
-
sections.push(entry.content);
|
|
345
|
-
sections.push("\n");
|
|
362
|
+
async syncWithRemote() {
|
|
363
|
+
await this.ensureInitialized();
|
|
364
|
+
try {
|
|
365
|
+
const remoteResult = await this.exec("git", ["remote"], this.config.workspaceRoot);
|
|
366
|
+
if (!remoteResult.stdout.trim()) {
|
|
367
|
+
this.log("No remote configured, skipping sync");
|
|
368
|
+
return;
|
|
346
369
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async function loadContextAtDepth(depth, options = {}) {
|
|
353
|
-
return contextLoader.load({ ...options, depth });
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const contextLoader$1 = {
|
|
357
|
-
__proto__: null,
|
|
358
|
-
ContextLoader: ContextLoader,
|
|
359
|
-
contextLoader: contextLoader,
|
|
360
|
-
loadContextAtDepth: loadContextAtDepth
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
function detectTaskPhase(history) {
|
|
364
|
-
if (history.length === 0) return "idle";
|
|
365
|
-
const recent = history.slice(-10);
|
|
366
|
-
const recentText = recent.map((h) => h.content).join(" ").toLowerCase();
|
|
367
|
-
if (recentText.includes("write") || recentText.includes("create") || recentText.includes("generating"))
|
|
368
|
-
return "generating";
|
|
369
|
-
if (recentText.includes("test") || recentText.includes("review") || recentText.includes("check"))
|
|
370
|
-
return "reviewing";
|
|
371
|
-
if (recentText.includes("edit") || recentText.includes("bash") || recentText.includes("run"))
|
|
372
|
-
return "executing";
|
|
373
|
-
if (recentText.includes("read") || recentText.includes("search") || recentText.includes("grep"))
|
|
374
|
-
return "exploring";
|
|
375
|
-
return "idle";
|
|
376
|
-
}
|
|
377
|
-
function extractFilePaths(content) {
|
|
378
|
-
const matches = content.match(/(?:\/[\w./\-]+\.\w+|[\w./\-]+\.(?:ts|js|json|md|py|go|rs))/g);
|
|
379
|
-
return matches ? [...new Set(matches)] : [];
|
|
380
|
-
}
|
|
381
|
-
class SessionIntelligence {
|
|
382
|
-
session;
|
|
383
|
-
constructor(session) {
|
|
384
|
-
this.session = session;
|
|
385
|
-
if (!session.intelligence) {
|
|
386
|
-
session.intelligence = {
|
|
387
|
-
taskPhase: "idle",
|
|
388
|
-
filesTouched: [],
|
|
389
|
-
keyDecisions: [],
|
|
390
|
-
toolCallCount: 0,
|
|
391
|
-
compressionWatermark: 0
|
|
392
|
-
};
|
|
370
|
+
await this.exec("git", ["pull", "--rebase"], this.config.workspaceRoot);
|
|
371
|
+
await this.exec("git", ["push"], this.config.workspaceRoot);
|
|
372
|
+
this.log("Synced with remote");
|
|
373
|
+
} catch (error) {
|
|
374
|
+
this.log(`Sync failed: ${error}`);
|
|
393
375
|
}
|
|
394
376
|
}
|
|
395
|
-
|
|
396
|
-
|
|
377
|
+
// ========================================================================
|
|
378
|
+
// Private Methods
|
|
379
|
+
// ========================================================================
|
|
380
|
+
async ensureInitialized() {
|
|
381
|
+
if (!this.initialized) {
|
|
382
|
+
await this.initialize();
|
|
383
|
+
}
|
|
397
384
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
*/
|
|
402
|
-
observe(entry) {
|
|
403
|
-
const intel = this.state;
|
|
404
|
-
if (!intel.userOriginalIntent && entry.role === "user" && entry.content.trim().length > 10) {
|
|
405
|
-
intel.userOriginalIntent = entry.content.slice(0, 500);
|
|
385
|
+
async getOrCreateAgentPath(agentId) {
|
|
386
|
+
if (!this.worktrees.has(agentId)) {
|
|
387
|
+
await this.createAgentWorktree(agentId);
|
|
406
388
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
389
|
+
return this.worktrees.get(agentId);
|
|
390
|
+
}
|
|
391
|
+
async loadExistingWorktrees() {
|
|
392
|
+
const agentsDir = join(this.config.workspaceRoot, "agents");
|
|
393
|
+
if (!existsSync(agentsDir)) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const { readdirSync, statSync } = await import('node:fs');
|
|
398
|
+
const entries = readdirSync(agentsDir);
|
|
399
|
+
for (const entry of entries) {
|
|
400
|
+
const entryPath = join(agentsDir, entry);
|
|
401
|
+
if (statSync(entryPath).isDirectory()) {
|
|
402
|
+
this.worktrees.set(entry, entryPath);
|
|
403
|
+
this.log(`Loaded existing worktree: ${entry}`);
|
|
404
|
+
}
|
|
411
405
|
}
|
|
406
|
+
} catch {
|
|
412
407
|
}
|
|
413
|
-
|
|
414
|
-
|
|
408
|
+
}
|
|
409
|
+
async batchCommit(agentId, message) {
|
|
410
|
+
const existing = this.pendingCommits.get(agentId);
|
|
411
|
+
if (existing) {
|
|
412
|
+
clearTimeout(existing);
|
|
415
413
|
}
|
|
416
|
-
|
|
414
|
+
return new Promise((resolve) => {
|
|
415
|
+
const timeout = setTimeout(async () => {
|
|
416
|
+
this.pendingCommits.delete(agentId);
|
|
417
|
+
const commitId = await this.commit(agentId, message);
|
|
418
|
+
resolve(commitId);
|
|
419
|
+
}, this.config.commitBatchInterval);
|
|
420
|
+
this.pendingCommits.set(agentId, timeout);
|
|
421
|
+
});
|
|
417
422
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
423
|
+
async commit(agentId, message) {
|
|
424
|
+
const agentPath = this.worktrees.get(agentId);
|
|
425
|
+
if (!agentPath) {
|
|
426
|
+
return "";
|
|
427
|
+
}
|
|
428
|
+
try {
|
|
429
|
+
await this.exec("git", ["add", "-A"], agentPath);
|
|
430
|
+
const statusResult = await this.exec("git", ["status", "--porcelain"], agentPath);
|
|
431
|
+
if (!statusResult.stdout.trim()) {
|
|
432
|
+
return "";
|
|
427
433
|
}
|
|
434
|
+
await this.exec("git", ["commit", "-m", message], agentPath);
|
|
435
|
+
const result = await this.exec("git", ["rev-parse", "HEAD"], agentPath);
|
|
436
|
+
return result.stdout.trim();
|
|
437
|
+
} catch {
|
|
438
|
+
return "";
|
|
428
439
|
}
|
|
429
440
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
441
|
+
async exec(cmd, args, cwd) {
|
|
442
|
+
const result = await K(cmd, args, { nodeOptions: { cwd } });
|
|
443
|
+
return {
|
|
444
|
+
stdout: result.stdout,
|
|
445
|
+
stderr: result.stderr
|
|
446
|
+
};
|
|
435
447
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
*/
|
|
440
|
-
getSystemPromptInjection() {
|
|
441
|
-
const intel = this.state;
|
|
442
|
-
const parts = ["<!-- session-intelligence -->"];
|
|
443
|
-
if (intel.userOriginalIntent) {
|
|
444
|
-
parts.push(`Original user intent: ${intel.userOriginalIntent}`);
|
|
445
|
-
}
|
|
446
|
-
if (intel.keyDecisions.length > 0) {
|
|
447
|
-
parts.push(`Key decisions: ${intel.keyDecisions.join("; ")}`);
|
|
448
|
-
}
|
|
449
|
-
if (intel.filesTouched.length > 0) {
|
|
450
|
-
parts.push(`Files touched: ${intel.filesTouched.slice(-20).join(", ")}`);
|
|
448
|
+
log(message) {
|
|
449
|
+
if (this.config.verbose) {
|
|
450
|
+
console.log(`[GitBackedState] ${message}`);
|
|
451
451
|
}
|
|
452
|
-
parts.push(`Task phase: ${intel.taskPhase} | Tool calls: ${intel.toolCallCount}`);
|
|
453
|
-
parts.push("<!-- /session-intelligence -->");
|
|
454
|
-
return parts.join("\n");
|
|
455
452
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return !!intel.userOriginalIntent || intel.toolCallCount > 3 || intel.filesTouched.length > 0;
|
|
453
|
+
}
|
|
454
|
+
let globalStateManager = null;
|
|
455
|
+
function getGlobalStateManager(config) {
|
|
456
|
+
if (!globalStateManager) {
|
|
457
|
+
globalStateManager = new GitBackedStateManager(config);
|
|
462
458
|
}
|
|
459
|
+
return globalStateManager;
|
|
463
460
|
}
|
|
464
461
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
462
|
+
const gitBackedState = {
|
|
463
|
+
__proto__: null,
|
|
464
|
+
GitBackedStateManager: GitBackedStateManager,
|
|
465
|
+
getGlobalStateManager: getGlobalStateManager
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
class ConvoyManager extends EventEmitter {
|
|
469
|
+
stateManager;
|
|
470
|
+
convoys = /* @__PURE__ */ new Map();
|
|
468
471
|
initialized = false;
|
|
469
|
-
|
|
470
|
-
constructor(config = {}) {
|
|
472
|
+
constructor(stateManager) {
|
|
471
473
|
super();
|
|
472
|
-
this.
|
|
473
|
-
workspaceRoot: config.workspaceRoot ?? join(homedir(), ".ccjk", "workspace"),
|
|
474
|
-
useWorktrees: config.useWorktrees ?? true,
|
|
475
|
-
autoCommit: config.autoCommit ?? true,
|
|
476
|
-
compressionLevel: config.compressionLevel ?? 6,
|
|
477
|
-
verbose: config.verbose ?? false,
|
|
478
|
-
commitBatchInterval: config.commitBatchInterval ?? 1e3
|
|
479
|
-
};
|
|
474
|
+
this.stateManager = stateManager ?? getGlobalStateManager();
|
|
480
475
|
}
|
|
481
476
|
/**
|
|
482
|
-
* Initialize
|
|
477
|
+
* Initialize convoy manager
|
|
483
478
|
*/
|
|
484
479
|
async initialize() {
|
|
485
480
|
if (this.initialized)
|
|
486
481
|
return;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
482
|
+
await this.stateManager.initialize();
|
|
483
|
+
await this.loadConvoys();
|
|
484
|
+
this.initialized = true;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Create new convoy
|
|
488
|
+
*/
|
|
489
|
+
async create(name, options = {}) {
|
|
490
|
+
await this.ensureInitialized();
|
|
491
|
+
const convoy = {
|
|
492
|
+
id: `cv-${nanoid(6)}`,
|
|
493
|
+
name,
|
|
494
|
+
description: options.description ?? "",
|
|
495
|
+
tasks: [],
|
|
496
|
+
status: "pending",
|
|
497
|
+
progress: 0,
|
|
498
|
+
totalTasks: 0,
|
|
499
|
+
completedTasks: 0,
|
|
500
|
+
failedTasks: 0,
|
|
501
|
+
cancelledTasks: 0,
|
|
502
|
+
skippedTasks: 0,
|
|
503
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
504
|
+
createdBy: options.createdBy ?? "system",
|
|
505
|
+
notifications: {
|
|
506
|
+
onComplete: options.notifyOnComplete ?? false,
|
|
507
|
+
onFailure: options.notifyOnFailure ?? true,
|
|
508
|
+
onProgress: options.notifyOnProgress ?? false,
|
|
509
|
+
notifyHuman: options.notifyHuman ?? false
|
|
510
|
+
},
|
|
511
|
+
metadata: options.metadata,
|
|
512
|
+
tags: options.tags ?? []
|
|
513
|
+
};
|
|
514
|
+
this.convoys.set(convoy.id, convoy);
|
|
515
|
+
await this.persistConvoy(convoy);
|
|
516
|
+
this.emit("convoy:created", convoy);
|
|
517
|
+
return convoy;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Add task to convoy
|
|
521
|
+
*/
|
|
522
|
+
async addTask(convoyId, title, options = {}) {
|
|
523
|
+
await this.ensureInitialized();
|
|
524
|
+
const convoy = this.convoys.get(convoyId);
|
|
525
|
+
if (!convoy) {
|
|
526
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
527
|
+
}
|
|
528
|
+
const task = {
|
|
529
|
+
id: `task-${nanoid(6)}`,
|
|
530
|
+
title,
|
|
531
|
+
description: options.description ?? "",
|
|
532
|
+
status: "pending",
|
|
533
|
+
assignedTo: options.assignedTo,
|
|
534
|
+
dependsOn: options.dependsOn ?? [],
|
|
535
|
+
metadata: options.metadata
|
|
536
|
+
};
|
|
537
|
+
convoy.tasks.push(task);
|
|
538
|
+
convoy.totalTasks = convoy.tasks.length;
|
|
539
|
+
this.updateProgress(convoy);
|
|
540
|
+
await this.persistConvoy(convoy);
|
|
541
|
+
return task;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Add multiple tasks to convoy
|
|
545
|
+
*/
|
|
546
|
+
async addTasks(convoyId, tasks) {
|
|
547
|
+
const addedTasks = [];
|
|
548
|
+
for (const { title, options } of tasks) {
|
|
549
|
+
const task = await this.addTask(convoyId, title, options);
|
|
550
|
+
addedTasks.push(task);
|
|
530
551
|
}
|
|
552
|
+
return addedTasks;
|
|
531
553
|
}
|
|
532
554
|
/**
|
|
533
|
-
*
|
|
555
|
+
* Start convoy execution
|
|
534
556
|
*/
|
|
535
|
-
async
|
|
557
|
+
async start(convoyId) {
|
|
536
558
|
await this.ensureInitialized();
|
|
537
|
-
const
|
|
538
|
-
if (
|
|
539
|
-
|
|
559
|
+
const convoy = this.convoys.get(convoyId);
|
|
560
|
+
if (!convoy) {
|
|
561
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
540
562
|
}
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const branchName = `agent/${agentId}`;
|
|
544
|
-
try {
|
|
545
|
-
await this.exec("git", ["checkout", "--orphan", branchName], workspaceRoot);
|
|
546
|
-
await this.exec("git", ["reset", "--hard"], workspaceRoot);
|
|
547
|
-
await this.exec("git", ["checkout", "main"], workspaceRoot).catch(() => {
|
|
548
|
-
return this.exec("git", ["checkout", "master"], workspaceRoot);
|
|
549
|
-
});
|
|
550
|
-
if (!existsSync(agentPath)) {
|
|
551
|
-
await this.exec("git", ["worktree", "add", agentPath, branchName], workspaceRoot);
|
|
552
|
-
}
|
|
553
|
-
} catch {
|
|
554
|
-
if (!existsSync(agentPath)) {
|
|
555
|
-
mkdirSync(agentPath, { recursive: true });
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
} else {
|
|
559
|
-
if (!existsSync(agentPath)) {
|
|
560
|
-
mkdirSync(agentPath, { recursive: true });
|
|
561
|
-
}
|
|
563
|
+
if (convoy.status !== "pending" && convoy.status !== "paused") {
|
|
564
|
+
throw new Error(`Cannot start convoy in status: ${convoy.status}`);
|
|
562
565
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
566
|
+
convoy.status = "in_progress";
|
|
567
|
+
convoy.startedAt = convoy.startedAt ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
568
|
+
await this.persistConvoy(convoy);
|
|
569
|
+
this.emit("convoy:started", convoy);
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* Start a task
|
|
573
|
+
*/
|
|
574
|
+
async startTask(convoyId, taskId, assignedTo) {
|
|
575
|
+
await this.ensureInitialized();
|
|
576
|
+
const convoy = this.convoys.get(convoyId);
|
|
577
|
+
if (!convoy) {
|
|
578
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
567
579
|
}
|
|
568
|
-
|
|
569
|
-
|
|
580
|
+
const task = convoy.tasks.find((t) => t.id === taskId);
|
|
581
|
+
if (!task) {
|
|
582
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
570
583
|
}
|
|
571
|
-
this.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
584
|
+
const unmetDeps = this.getUnmetDependencies(convoy, task);
|
|
585
|
+
if (unmetDeps.length > 0) {
|
|
586
|
+
throw new Error(`Task has unmet dependencies: ${unmetDeps.join(", ")}`);
|
|
587
|
+
}
|
|
588
|
+
task.status = "in_progress";
|
|
589
|
+
task.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
590
|
+
if (assignedTo) {
|
|
591
|
+
task.assignedTo = assignedTo;
|
|
592
|
+
}
|
|
593
|
+
if (convoy.status === "pending") {
|
|
594
|
+
convoy.status = "in_progress";
|
|
595
|
+
convoy.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
596
|
+
}
|
|
597
|
+
await this.persistConvoy(convoy);
|
|
598
|
+
this.emit("task:started", convoy, task);
|
|
575
599
|
}
|
|
576
600
|
/**
|
|
577
|
-
*
|
|
601
|
+
* Complete a task
|
|
578
602
|
*/
|
|
579
|
-
async
|
|
603
|
+
async completeTask(convoyId, taskId, result) {
|
|
580
604
|
await this.ensureInitialized();
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
if (existsSync(stateFile)) {
|
|
585
|
-
try {
|
|
586
|
-
existingState = JSON.parse(readFileSync(stateFile, "utf-8"));
|
|
587
|
-
} catch {
|
|
588
|
-
}
|
|
605
|
+
const convoy = this.convoys.get(convoyId);
|
|
606
|
+
if (!convoy) {
|
|
607
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
589
608
|
}
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
609
|
+
const task = convoy.tasks.find((t) => t.id === taskId);
|
|
610
|
+
if (!task) {
|
|
611
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
612
|
+
}
|
|
613
|
+
task.status = "completed";
|
|
614
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
615
|
+
task.result = result;
|
|
616
|
+
convoy.completedTasks++;
|
|
617
|
+
this.updateProgress(convoy);
|
|
618
|
+
this.checkConvoyCompletion(convoy);
|
|
619
|
+
await this.persistConvoy(convoy);
|
|
620
|
+
this.emit("task:completed", convoy, task);
|
|
621
|
+
if (convoy.notifications.onProgress) {
|
|
622
|
+
this.emit("convoy:progress", convoy, convoy.progress);
|
|
599
623
|
}
|
|
600
|
-
this.emit("state:saved", agentId, commitId);
|
|
601
|
-
this.log(`Saved state for agent: ${agentId}`);
|
|
602
|
-
return commitId;
|
|
603
624
|
}
|
|
604
625
|
/**
|
|
605
|
-
*
|
|
626
|
+
* Fail a task
|
|
606
627
|
*/
|
|
607
|
-
async
|
|
628
|
+
async failTask(convoyId, taskId, error) {
|
|
608
629
|
await this.ensureInitialized();
|
|
609
|
-
const
|
|
610
|
-
if (!
|
|
611
|
-
|
|
630
|
+
const convoy = this.convoys.get(convoyId);
|
|
631
|
+
if (!convoy) {
|
|
632
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
612
633
|
}
|
|
613
|
-
const
|
|
614
|
-
if (!
|
|
615
|
-
|
|
634
|
+
const task = convoy.tasks.find((t) => t.id === taskId);
|
|
635
|
+
if (!task) {
|
|
636
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
616
637
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
638
|
+
task.status = "failed";
|
|
639
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
640
|
+
task.error = error;
|
|
641
|
+
convoy.failedTasks++;
|
|
642
|
+
this.updateProgress(convoy);
|
|
643
|
+
await this.skipDependentTasks(convoy, taskId);
|
|
644
|
+
this.checkConvoyCompletion(convoy);
|
|
645
|
+
await this.persistConvoy(convoy);
|
|
646
|
+
this.emit("task:failed", convoy, task);
|
|
647
|
+
if (convoy.notifications.onFailure) {
|
|
648
|
+
this.emit("convoy:failed", convoy);
|
|
624
649
|
}
|
|
625
650
|
}
|
|
626
651
|
/**
|
|
627
|
-
*
|
|
652
|
+
* Cancel a task
|
|
628
653
|
*/
|
|
629
|
-
async
|
|
654
|
+
async cancelTask(convoyId, taskId) {
|
|
630
655
|
await this.ensureInitialized();
|
|
631
|
-
const
|
|
632
|
-
if (!
|
|
633
|
-
throw new Error(`
|
|
656
|
+
const convoy = this.convoys.get(convoyId);
|
|
657
|
+
if (!convoy) {
|
|
658
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
634
659
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.log(`Rolled back agent ${agentId} to commit ${commitId}`);
|
|
639
|
-
} catch (error) {
|
|
640
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
641
|
-
this.emit("error", err);
|
|
642
|
-
throw err;
|
|
660
|
+
const task = convoy.tasks.find((t) => t.id === taskId);
|
|
661
|
+
if (!task) {
|
|
662
|
+
throw new Error(`Task not found: ${taskId}`);
|
|
643
663
|
}
|
|
664
|
+
task.status = "cancelled";
|
|
665
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
666
|
+
convoy.cancelledTasks++;
|
|
667
|
+
this.updateProgress(convoy);
|
|
668
|
+
await this.skipDependentTasks(convoy, taskId);
|
|
669
|
+
this.checkConvoyCompletion(convoy);
|
|
670
|
+
await this.persistConvoy(convoy);
|
|
671
|
+
this.emit("task:cancelled", convoy, task);
|
|
644
672
|
}
|
|
645
673
|
/**
|
|
646
|
-
*
|
|
674
|
+
* Pause convoy
|
|
647
675
|
*/
|
|
648
|
-
async
|
|
676
|
+
async pause(convoyId) {
|
|
649
677
|
await this.ensureInitialized();
|
|
650
|
-
const
|
|
651
|
-
if (!
|
|
652
|
-
|
|
678
|
+
const convoy = this.convoys.get(convoyId);
|
|
679
|
+
if (!convoy) {
|
|
680
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
653
681
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
"git",
|
|
657
|
-
["log", `--max-count=${limit}`, "--format=%H|%s|%ct", "--", "state.json"],
|
|
658
|
-
agentPath
|
|
659
|
-
);
|
|
660
|
-
const snapshots = [];
|
|
661
|
-
for (const line of result.stdout.split("\n").filter(Boolean)) {
|
|
662
|
-
const [commitId, message, timestamp] = line.split("|");
|
|
663
|
-
const stateResult = await this.exec(
|
|
664
|
-
"git",
|
|
665
|
-
["show", `${commitId}:state.json`],
|
|
666
|
-
agentPath
|
|
667
|
-
).catch(() => ({ stdout: "{}" }));
|
|
668
|
-
let state;
|
|
669
|
-
try {
|
|
670
|
-
state = JSON.parse(stateResult.stdout);
|
|
671
|
-
} catch {
|
|
672
|
-
state = {};
|
|
673
|
-
}
|
|
674
|
-
snapshots.push({
|
|
675
|
-
id: nanoid(),
|
|
676
|
-
agentId,
|
|
677
|
-
commitId,
|
|
678
|
-
timestamp: Number.parseInt(timestamp, 10) * 1e3,
|
|
679
|
-
message,
|
|
680
|
-
state
|
|
681
|
-
});
|
|
682
|
-
}
|
|
683
|
-
return snapshots;
|
|
684
|
-
} catch {
|
|
685
|
-
return [];
|
|
682
|
+
if (convoy.status !== "in_progress") {
|
|
683
|
+
throw new Error(`Cannot pause convoy in status: ${convoy.status}`);
|
|
686
684
|
}
|
|
685
|
+
convoy.status = "paused";
|
|
686
|
+
await this.persistConvoy(convoy);
|
|
687
|
+
this.emit("convoy:paused", convoy);
|
|
687
688
|
}
|
|
688
689
|
/**
|
|
689
|
-
*
|
|
690
|
+
* Resume convoy
|
|
690
691
|
*/
|
|
691
|
-
async
|
|
692
|
+
async resume(convoyId) {
|
|
693
|
+
await this.ensureInitialized();
|
|
694
|
+
const convoy = this.convoys.get(convoyId);
|
|
695
|
+
if (!convoy) {
|
|
696
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
697
|
+
}
|
|
698
|
+
if (convoy.status !== "paused") {
|
|
699
|
+
throw new Error(`Cannot resume convoy in status: ${convoy.status}`);
|
|
700
|
+
}
|
|
701
|
+
convoy.status = "in_progress";
|
|
702
|
+
await this.persistConvoy(convoy);
|
|
703
|
+
this.emit("convoy:resumed", convoy);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Cancel convoy
|
|
707
|
+
*/
|
|
708
|
+
async cancel(convoyId) {
|
|
692
709
|
await this.ensureInitialized();
|
|
693
|
-
const
|
|
694
|
-
if (!
|
|
695
|
-
|
|
710
|
+
const convoy = this.convoys.get(convoyId);
|
|
711
|
+
if (!convoy) {
|
|
712
|
+
throw new Error(`Convoy not found: ${convoyId}`);
|
|
696
713
|
}
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
714
|
+
convoy.status = "cancelled";
|
|
715
|
+
convoy.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
716
|
+
for (const task of convoy.tasks) {
|
|
717
|
+
if (task.status === "pending" || task.status === "in_progress") {
|
|
718
|
+
task.status = "cancelled";
|
|
719
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
720
|
+
convoy.cancelledTasks++;
|
|
700
721
|
}
|
|
701
|
-
this.worktrees.delete(agentId);
|
|
702
|
-
this.emit("worktree:removed", agentId);
|
|
703
|
-
this.log(`Cleaned up worktree for agent: ${agentId}`);
|
|
704
|
-
} catch (error) {
|
|
705
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
706
|
-
this.emit("error", err);
|
|
707
|
-
throw err;
|
|
708
722
|
}
|
|
723
|
+
this.updateProgress(convoy);
|
|
724
|
+
await this.persistConvoy(convoy);
|
|
725
|
+
this.emit("convoy:cancelled", convoy);
|
|
709
726
|
}
|
|
710
727
|
/**
|
|
711
|
-
* Get
|
|
728
|
+
* Get convoy by ID
|
|
712
729
|
*/
|
|
713
|
-
|
|
714
|
-
return
|
|
730
|
+
get(convoyId) {
|
|
731
|
+
return this.convoys.get(convoyId);
|
|
715
732
|
}
|
|
716
733
|
/**
|
|
717
|
-
*
|
|
734
|
+
* List all convoys
|
|
718
735
|
*/
|
|
719
|
-
|
|
720
|
-
|
|
736
|
+
list(filter) {
|
|
737
|
+
let convoys = Array.from(this.convoys.values());
|
|
738
|
+
if (filter) {
|
|
739
|
+
if (filter.status) {
|
|
740
|
+
convoys = convoys.filter((c) => c.status === filter.status);
|
|
741
|
+
}
|
|
742
|
+
if (filter.createdBy) {
|
|
743
|
+
convoys = convoys.filter((c) => c.createdBy === filter.createdBy);
|
|
744
|
+
}
|
|
745
|
+
if (filter.tags && filter.tags.length > 0) {
|
|
746
|
+
convoys = convoys.filter(
|
|
747
|
+
(c) => filter.tags.some((tag) => c.tags.includes(tag))
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return convoys.sort(
|
|
752
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
753
|
+
);
|
|
721
754
|
}
|
|
722
755
|
/**
|
|
723
|
-
* Get
|
|
756
|
+
* Get active convoys
|
|
724
757
|
*/
|
|
725
|
-
|
|
726
|
-
return this.
|
|
758
|
+
getActive() {
|
|
759
|
+
return this.list({ status: "in_progress" });
|
|
727
760
|
}
|
|
728
761
|
/**
|
|
729
|
-
* Get
|
|
762
|
+
* Get next available tasks (no unmet dependencies)
|
|
730
763
|
*/
|
|
731
|
-
|
|
732
|
-
|
|
764
|
+
getNextTasks(convoyId) {
|
|
765
|
+
const convoy = this.convoys.get(convoyId);
|
|
766
|
+
if (!convoy)
|
|
767
|
+
return [];
|
|
768
|
+
return convoy.tasks.filter((task) => {
|
|
769
|
+
if (task.status !== "pending")
|
|
770
|
+
return false;
|
|
771
|
+
return this.getUnmetDependencies(convoy, task).length === 0;
|
|
772
|
+
});
|
|
733
773
|
}
|
|
734
774
|
/**
|
|
735
|
-
* Get
|
|
775
|
+
* Get convoy summary
|
|
736
776
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
);
|
|
749
|
-
const commits = result.stdout.split("\n").filter(Boolean).map((line) => {
|
|
750
|
-
const [id, message, timestamp] = line.split("|");
|
|
751
|
-
return {
|
|
752
|
-
id,
|
|
753
|
-
message,
|
|
754
|
-
timestamp: Number.parseInt(timestamp, 10) * 1e3
|
|
755
|
-
};
|
|
756
|
-
});
|
|
757
|
-
const countResult = await this.exec(
|
|
758
|
-
"git",
|
|
759
|
-
["rev-list", "--count", "HEAD", "--", "state.json"],
|
|
760
|
-
agentPath
|
|
761
|
-
).catch(() => ({ stdout: "0" }));
|
|
762
|
-
return {
|
|
763
|
-
agentId,
|
|
764
|
-
commits,
|
|
765
|
-
totalCommits: Number.parseInt(countResult.stdout.trim(), 10) || commits.length
|
|
766
|
-
};
|
|
767
|
-
} catch {
|
|
768
|
-
return { agentId, commits: [], totalCommits: 0 };
|
|
777
|
+
getSummary(convoyId) {
|
|
778
|
+
const convoy = this.convoys.get(convoyId);
|
|
779
|
+
if (!convoy)
|
|
780
|
+
return "Convoy not found";
|
|
781
|
+
const lines = [
|
|
782
|
+
`Convoy: ${convoy.name} (${convoy.id})`,
|
|
783
|
+
`Status: ${convoy.status}`,
|
|
784
|
+
`Progress: ${convoy.progress}%`,
|
|
785
|
+
`Tasks: ${convoy.completedTasks}/${convoy.totalTasks} completed`
|
|
786
|
+
];
|
|
787
|
+
if (convoy.failedTasks > 0) {
|
|
788
|
+
lines.push(`Failed: ${convoy.failedTasks}`);
|
|
769
789
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
* Create a named snapshot of current state
|
|
773
|
-
*/
|
|
774
|
-
async createSnapshot(agentId, description) {
|
|
775
|
-
await this.ensureInitialized();
|
|
776
|
-
const agentPath = this.worktrees.get(agentId);
|
|
777
|
-
if (!agentPath) {
|
|
778
|
-
throw new Error(`Agent not found: ${agentId}`);
|
|
790
|
+
if (convoy.cancelledTasks > 0) {
|
|
791
|
+
lines.push(`Cancelled: ${convoy.cancelledTasks}`);
|
|
779
792
|
}
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
const result = await this.exec("git", ["rev-parse", "HEAD"], agentPath);
|
|
783
|
-
const currentCommitId = result.stdout.trim();
|
|
784
|
-
const state2 = await this.loadState(agentId);
|
|
785
|
-
return {
|
|
786
|
-
id: nanoid(),
|
|
787
|
-
agentId,
|
|
788
|
-
commitId: currentCommitId,
|
|
789
|
-
timestamp: Date.now(),
|
|
790
|
-
message: description,
|
|
791
|
-
state: state2 ?? {}
|
|
792
|
-
};
|
|
793
|
+
if (convoy.skippedTasks > 0) {
|
|
794
|
+
lines.push(`Skipped: ${convoy.skippedTasks}`);
|
|
793
795
|
}
|
|
794
|
-
|
|
795
|
-
return {
|
|
796
|
-
id: nanoid(),
|
|
797
|
-
agentId,
|
|
798
|
-
commitId,
|
|
799
|
-
timestamp: Date.now(),
|
|
800
|
-
message: description,
|
|
801
|
-
state: state ?? {}
|
|
802
|
-
};
|
|
803
|
-
}
|
|
804
|
-
/**
|
|
805
|
-
* Restore state from a snapshot
|
|
806
|
-
*/
|
|
807
|
-
async restoreSnapshot(agentId, snapshotId) {
|
|
808
|
-
await this.rollback(agentId, snapshotId);
|
|
796
|
+
return lines.join("\n");
|
|
809
797
|
}
|
|
810
798
|
/**
|
|
811
|
-
*
|
|
799
|
+
* Delete convoy
|
|
812
800
|
*/
|
|
813
|
-
async
|
|
801
|
+
async delete(convoyId) {
|
|
814
802
|
await this.ensureInitialized();
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
}
|
|
821
|
-
await this.exec("git", ["pull", "--rebase"], this.config.workspaceRoot);
|
|
822
|
-
await this.exec("git", ["push"], this.config.workspaceRoot);
|
|
823
|
-
this.log("Synced with remote");
|
|
824
|
-
} catch (error) {
|
|
825
|
-
this.log(`Sync failed: ${error}`);
|
|
826
|
-
}
|
|
803
|
+
const convoy = this.convoys.get(convoyId);
|
|
804
|
+
if (!convoy)
|
|
805
|
+
return false;
|
|
806
|
+
this.convoys.delete(convoyId);
|
|
807
|
+
return true;
|
|
827
808
|
}
|
|
828
809
|
// ========================================================================
|
|
829
810
|
// Private Methods
|
|
@@ -833,505 +814,558 @@ Thumbs.db
|
|
|
833
814
|
await this.initialize();
|
|
834
815
|
}
|
|
835
816
|
}
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
}
|
|
840
|
-
return this.worktrees.get(agentId);
|
|
817
|
+
updateProgress(convoy) {
|
|
818
|
+
const processed = convoy.completedTasks + convoy.failedTasks + convoy.cancelledTasks + convoy.skippedTasks;
|
|
819
|
+
convoy.progress = convoy.totalTasks > 0 ? Math.round(processed / convoy.totalTasks * 100) : 0;
|
|
841
820
|
}
|
|
842
|
-
|
|
843
|
-
const
|
|
844
|
-
if (
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
this.
|
|
821
|
+
checkConvoyCompletion(convoy) {
|
|
822
|
+
const processed = convoy.completedTasks + convoy.failedTasks + convoy.cancelledTasks + convoy.skippedTasks;
|
|
823
|
+
if (processed === convoy.totalTasks) {
|
|
824
|
+
convoy.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
825
|
+
if (convoy.failedTasks === 0 && convoy.cancelledTasks === 0) {
|
|
826
|
+
convoy.status = "completed";
|
|
827
|
+
if (convoy.notifications.onComplete) {
|
|
828
|
+
this.emit("convoy:completed", convoy);
|
|
829
|
+
}
|
|
830
|
+
} else {
|
|
831
|
+
convoy.status = "failed";
|
|
832
|
+
if (convoy.notifications.onFailure) {
|
|
833
|
+
this.emit("convoy:failed", convoy);
|
|
855
834
|
}
|
|
856
835
|
}
|
|
857
|
-
} catch {
|
|
858
836
|
}
|
|
859
837
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
return new Promise((resolve) => {
|
|
866
|
-
const timeout = setTimeout(async () => {
|
|
867
|
-
this.pendingCommits.delete(agentId);
|
|
868
|
-
const commitId = await this.commit(agentId, message);
|
|
869
|
-
resolve(commitId);
|
|
870
|
-
}, this.config.commitBatchInterval);
|
|
871
|
-
this.pendingCommits.set(agentId, timeout);
|
|
838
|
+
getUnmetDependencies(convoy, task) {
|
|
839
|
+
return task.dependsOn.filter((depId) => {
|
|
840
|
+
const depTask = convoy.tasks.find((t) => t.id === depId);
|
|
841
|
+
return !depTask || depTask.status !== "completed";
|
|
872
842
|
});
|
|
873
843
|
}
|
|
874
|
-
async
|
|
875
|
-
const
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
}
|
|
885
|
-
await this.exec("git", ["commit", "-m", message], agentPath);
|
|
886
|
-
const result = await this.exec("git", ["rev-parse", "HEAD"], agentPath);
|
|
887
|
-
return result.stdout.trim();
|
|
888
|
-
} catch {
|
|
889
|
-
return "";
|
|
844
|
+
async skipDependentTasks(convoy, failedTaskId) {
|
|
845
|
+
for (const task of convoy.tasks) {
|
|
846
|
+
if (task.status === "pending" && task.dependsOn.includes(failedTaskId)) {
|
|
847
|
+
task.status = "skipped";
|
|
848
|
+
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
849
|
+
task.error = `Skipped due to failed dependency: ${failedTaskId}`;
|
|
850
|
+
convoy.skippedTasks++;
|
|
851
|
+
this.emit("task:skipped", convoy, task);
|
|
852
|
+
await this.skipDependentTasks(convoy, task.id);
|
|
853
|
+
}
|
|
890
854
|
}
|
|
891
855
|
}
|
|
892
|
-
async
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
console.log(`[GitBackedState] ${message}`);
|
|
856
|
+
async loadConvoys() {
|
|
857
|
+
for (const agentId of this.stateManager.getAgentIds()) {
|
|
858
|
+
if (agentId.startsWith("convoy-")) {
|
|
859
|
+
const state = await this.stateManager.loadState(agentId);
|
|
860
|
+
if (state) {
|
|
861
|
+
const convoy = state;
|
|
862
|
+
this.convoys.set(convoy.id, convoy);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
902
865
|
}
|
|
903
866
|
}
|
|
867
|
+
async persistConvoy(convoy) {
|
|
868
|
+
await this.stateManager.createAgentWorktree(`convoy-${convoy.id}`);
|
|
869
|
+
await this.stateManager.saveState(`convoy-${convoy.id}`, convoy);
|
|
870
|
+
}
|
|
904
871
|
}
|
|
905
|
-
let
|
|
906
|
-
function
|
|
907
|
-
if (!
|
|
908
|
-
|
|
872
|
+
let globalConvoyManager = null;
|
|
873
|
+
function getGlobalConvoyManager() {
|
|
874
|
+
if (!globalConvoyManager) {
|
|
875
|
+
globalConvoyManager = new ConvoyManager();
|
|
909
876
|
}
|
|
910
|
-
return
|
|
877
|
+
return globalConvoyManager;
|
|
911
878
|
}
|
|
912
879
|
|
|
913
|
-
const
|
|
880
|
+
const convoyManager = {
|
|
914
881
|
__proto__: null,
|
|
915
|
-
|
|
916
|
-
|
|
882
|
+
ConvoyManager: ConvoyManager,
|
|
883
|
+
getGlobalConvoyManager: getGlobalConvoyManager
|
|
917
884
|
};
|
|
918
885
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
886
|
+
const DEPTH_TOKEN_BUDGETS = {
|
|
887
|
+
L0: 100,
|
|
888
|
+
// Abstract summary only
|
|
889
|
+
L1: 2e3,
|
|
890
|
+
// Overview
|
|
891
|
+
L2: Number.POSITIVE_INFINITY
|
|
892
|
+
// Full content
|
|
893
|
+
};
|
|
894
|
+
const PHASE_BUDGET_MULTIPLIERS = {
|
|
895
|
+
exploring: 2,
|
|
896
|
+
executing: 0.6,
|
|
897
|
+
generating: 0.4,
|
|
898
|
+
reviewing: 1.2,
|
|
899
|
+
idle: 1
|
|
900
|
+
};
|
|
901
|
+
const CHARS_PER_TOKEN = 4;
|
|
902
|
+
function estimateTokens(text) {
|
|
903
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
904
|
+
}
|
|
905
|
+
function truncateToDepth(content, depth) {
|
|
906
|
+
if (depth === "L2")
|
|
907
|
+
return content;
|
|
908
|
+
const maxChars = DEPTH_TOKEN_BUDGETS[depth] * CHARS_PER_TOKEN;
|
|
909
|
+
if (content.length <= maxChars)
|
|
910
|
+
return content;
|
|
911
|
+
if (depth === "L0") {
|
|
912
|
+
const firstPara = content.split(/\n\n/)[0];
|
|
913
|
+
return firstPara.length <= maxChars ? firstPara : content.slice(0, maxChars);
|
|
936
914
|
}
|
|
915
|
+
return `${content.slice(0, maxChars)}
|
|
916
|
+
... (truncated, use L2 for full content)`;
|
|
917
|
+
}
|
|
918
|
+
class ContextLoader {
|
|
919
|
+
cache = /* @__PURE__ */ new Map();
|
|
920
|
+
defaultMaxSize = 1e5;
|
|
921
|
+
// 100KB default
|
|
922
|
+
defaultTokenBudget = 8e3;
|
|
923
|
+
// ~32KB at 4 chars/token
|
|
937
924
|
/**
|
|
938
|
-
*
|
|
925
|
+
* Resolve effective depth from options.
|
|
926
|
+
* If depth is explicit, use it. Otherwise derive from tokenBudget.
|
|
939
927
|
*/
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
progress: 0,
|
|
949
|
-
totalTasks: 0,
|
|
950
|
-
completedTasks: 0,
|
|
951
|
-
failedTasks: 0,
|
|
952
|
-
cancelledTasks: 0,
|
|
953
|
-
skippedTasks: 0,
|
|
954
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
955
|
-
createdBy: options.createdBy ?? "system",
|
|
956
|
-
notifications: {
|
|
957
|
-
onComplete: options.notifyOnComplete ?? false,
|
|
958
|
-
onFailure: options.notifyOnFailure ?? true,
|
|
959
|
-
onProgress: options.notifyOnProgress ?? false,
|
|
960
|
-
notifyHuman: options.notifyHuman ?? false
|
|
961
|
-
},
|
|
962
|
-
metadata: options.metadata,
|
|
963
|
-
tags: options.tags ?? []
|
|
964
|
-
};
|
|
965
|
-
this.convoys.set(convoy.id, convoy);
|
|
966
|
-
await this.persistConvoy(convoy);
|
|
967
|
-
this.emit("convoy:created", convoy);
|
|
968
|
-
return convoy;
|
|
928
|
+
resolveDepth(tokenBudget, explicitDepth) {
|
|
929
|
+
if (explicitDepth)
|
|
930
|
+
return explicitDepth;
|
|
931
|
+
if (tokenBudget <= DEPTH_TOKEN_BUDGETS.L0)
|
|
932
|
+
return "L0";
|
|
933
|
+
if (tokenBudget <= DEPTH_TOKEN_BUDGETS.L1)
|
|
934
|
+
return "L1";
|
|
935
|
+
return "L2";
|
|
969
936
|
}
|
|
970
937
|
/**
|
|
971
|
-
*
|
|
938
|
+
* Load context hierarchically with OpenViking-style tiered depth control.
|
|
939
|
+
* Pass tokenBudget to automatically select L0/L1/L2 depth.
|
|
972
940
|
*/
|
|
973
|
-
async
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
941
|
+
async load(options = {}) {
|
|
942
|
+
const {
|
|
943
|
+
projectRoot = process.cwd(),
|
|
944
|
+
maxSize = this.defaultMaxSize,
|
|
945
|
+
tokenBudget,
|
|
946
|
+
depth: explicitDepth,
|
|
947
|
+
layers = ["project", "domain", "task", "execution"],
|
|
948
|
+
task
|
|
949
|
+
} = options;
|
|
950
|
+
const phaseMultiplier = options.taskPhase ? PHASE_BUDGET_MULTIPLIERS[options.taskPhase] ?? 1 : 1;
|
|
951
|
+
const effectiveBudget = Math.round((tokenBudget ?? this.defaultTokenBudget) * phaseMultiplier);
|
|
952
|
+
const depth = this.resolveDepth(effectiveBudget, explicitDepth);
|
|
953
|
+
const cacheKey = this.getCacheKey(projectRoot, layers, task?.id, depth);
|
|
954
|
+
const cached = this.cache.get(cacheKey);
|
|
955
|
+
if (cached && Date.now() - cached.loadedAt < 6e4) {
|
|
956
|
+
executionTracer.logEvent("context-load", {
|
|
957
|
+
source: "cache",
|
|
958
|
+
layers: layers.length,
|
|
959
|
+
size: cached.totalSize,
|
|
960
|
+
tokens: cached.totalTokens,
|
|
961
|
+
depth
|
|
962
|
+
});
|
|
963
|
+
return cached;
|
|
978
964
|
}
|
|
979
|
-
const
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
dependsOn: options.dependsOn ?? [],
|
|
986
|
-
metadata: options.metadata
|
|
965
|
+
const context = {
|
|
966
|
+
layers: /* @__PURE__ */ new Map(),
|
|
967
|
+
totalSize: 0,
|
|
968
|
+
totalTokens: 0,
|
|
969
|
+
loadedAt: Date.now(),
|
|
970
|
+
depth
|
|
987
971
|
};
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
const addedTasks = [];
|
|
999
|
-
for (const { title, options } of tasks) {
|
|
1000
|
-
const task = await this.addTask(convoyId, title, options);
|
|
1001
|
-
addedTasks.push(task);
|
|
1002
|
-
}
|
|
1003
|
-
return addedTasks;
|
|
1004
|
-
}
|
|
1005
|
-
/**
|
|
1006
|
-
* Start convoy execution
|
|
1007
|
-
*/
|
|
1008
|
-
async start(convoyId) {
|
|
1009
|
-
await this.ensureInitialized();
|
|
1010
|
-
const convoy = this.convoys.get(convoyId);
|
|
1011
|
-
if (!convoy) {
|
|
1012
|
-
throw new Error(`Convoy not found: ${convoyId}`);
|
|
1013
|
-
}
|
|
1014
|
-
if (convoy.status !== "pending" && convoy.status !== "paused") {
|
|
1015
|
-
throw new Error(`Cannot start convoy in status: ${convoy.status}`);
|
|
972
|
+
for (const layer of layers) {
|
|
973
|
+
const entries = await this.loadLayer(layer, projectRoot, task, depth);
|
|
974
|
+
if (entries.length > 0) {
|
|
975
|
+
context.layers.set(layer, entries);
|
|
976
|
+
context.totalSize += entries.reduce((sum, e) => sum + e.content.length, 0);
|
|
977
|
+
context.totalTokens += entries.reduce((sum, e) => sum + e.tokenEstimate, 0);
|
|
978
|
+
}
|
|
979
|
+
if (context.totalSize > maxSize || context.totalTokens > effectiveBudget) {
|
|
980
|
+
break;
|
|
981
|
+
}
|
|
1016
982
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
983
|
+
this.cache.set(cacheKey, context);
|
|
984
|
+
executionTracer.logEvent("context-load", {
|
|
985
|
+
source: "fresh",
|
|
986
|
+
layers: context.layers.size,
|
|
987
|
+
size: context.totalSize,
|
|
988
|
+
tokens: context.totalTokens,
|
|
989
|
+
depth
|
|
990
|
+
});
|
|
991
|
+
return context;
|
|
1021
992
|
}
|
|
1022
993
|
/**
|
|
1023
|
-
*
|
|
994
|
+
* Load a specific context layer
|
|
1024
995
|
*/
|
|
1025
|
-
async
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
throw new Error(`Task has unmet dependencies: ${unmetDeps.join(", ")}`);
|
|
1038
|
-
}
|
|
1039
|
-
task.status = "in_progress";
|
|
1040
|
-
task.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1041
|
-
if (assignedTo) {
|
|
1042
|
-
task.assignedTo = assignedTo;
|
|
1043
|
-
}
|
|
1044
|
-
if (convoy.status === "pending") {
|
|
1045
|
-
convoy.status = "in_progress";
|
|
1046
|
-
convoy.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
996
|
+
async loadLayer(layer, projectRoot, task, depth = "L2") {
|
|
997
|
+
switch (layer) {
|
|
998
|
+
case "project":
|
|
999
|
+
return this.loadProjectContext(projectRoot, depth);
|
|
1000
|
+
case "domain":
|
|
1001
|
+
return this.loadDomainContext(projectRoot, task, depth);
|
|
1002
|
+
case "task":
|
|
1003
|
+
return this.loadTaskContext(task, depth);
|
|
1004
|
+
case "execution":
|
|
1005
|
+
return this.loadExecutionContext(task, depth);
|
|
1006
|
+
default:
|
|
1007
|
+
return [];
|
|
1047
1008
|
}
|
|
1048
|
-
await this.persistConvoy(convoy);
|
|
1049
|
-
this.emit("task:started", convoy, task);
|
|
1050
1009
|
}
|
|
1051
1010
|
/**
|
|
1052
|
-
*
|
|
1011
|
+
* Load project-level context
|
|
1012
|
+
* - README.md
|
|
1013
|
+
* - CLAUDE.md
|
|
1014
|
+
* - package.json
|
|
1015
|
+
* - tsconfig.json
|
|
1053
1016
|
*/
|
|
1054
|
-
async
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
if (convoy.notifications.onProgress) {
|
|
1073
|
-
this.emit("convoy:progress", convoy, convoy.progress);
|
|
1017
|
+
async loadProjectContext(projectRoot, depth = "L2") {
|
|
1018
|
+
const entries = [];
|
|
1019
|
+
const files = ["README.md", "CLAUDE.md", "package.json", "tsconfig.json"];
|
|
1020
|
+
for (const file of files) {
|
|
1021
|
+
const path = join(projectRoot, file);
|
|
1022
|
+
if (existsSync(path)) {
|
|
1023
|
+
const raw = readFileSync(path, "utf-8");
|
|
1024
|
+
const content = truncateToDepth(raw, depth);
|
|
1025
|
+
entries.push({
|
|
1026
|
+
layer: "project",
|
|
1027
|
+
source: file,
|
|
1028
|
+
content,
|
|
1029
|
+
priority: 100,
|
|
1030
|
+
depth,
|
|
1031
|
+
tokenEstimate: estimateTokens(content),
|
|
1032
|
+
metadata: { path }
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1074
1035
|
}
|
|
1036
|
+
return entries;
|
|
1075
1037
|
}
|
|
1076
1038
|
/**
|
|
1077
|
-
*
|
|
1039
|
+
* Load domain-specific context
|
|
1040
|
+
* Based on task type/domain, load relevant files
|
|
1078
1041
|
*/
|
|
1079
|
-
async
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1042
|
+
async loadDomainContext(projectRoot, task, depth = "L2") {
|
|
1043
|
+
if (!task)
|
|
1044
|
+
return [];
|
|
1045
|
+
const entries = [];
|
|
1046
|
+
const structure = await fsParadigm.detect(projectRoot);
|
|
1047
|
+
const domain = this.inferDomain(task);
|
|
1048
|
+
if (!domain) {
|
|
1049
|
+
const sourceFiles = fsParadigm.getFilesByRole(structure, "source");
|
|
1050
|
+
for (const file of sourceFiles.slice(0, 3)) {
|
|
1051
|
+
try {
|
|
1052
|
+
const raw = readFileSync(file, "utf-8");
|
|
1053
|
+
const content = truncateToDepth(raw, depth);
|
|
1054
|
+
entries.push({
|
|
1055
|
+
layer: "domain",
|
|
1056
|
+
source: file.replace(projectRoot, ""),
|
|
1057
|
+
content,
|
|
1058
|
+
priority: 75,
|
|
1059
|
+
depth,
|
|
1060
|
+
tokenEstimate: estimateTokens(content),
|
|
1061
|
+
metadata: { paradigm: structure.paradigm, path: file }
|
|
1062
|
+
});
|
|
1063
|
+
} catch {
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
return entries;
|
|
1088
1067
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1068
|
+
const patterns = this.getDomainPatterns(domain, structure.paradigm);
|
|
1069
|
+
for (const pattern of patterns) {
|
|
1070
|
+
const files = await glob([pattern], {
|
|
1071
|
+
cwd: projectRoot,
|
|
1072
|
+
absolute: true,
|
|
1073
|
+
ignore: ["**/node_modules/**", "**/dist/**", "**/.git/**"]
|
|
1074
|
+
});
|
|
1075
|
+
for (const file of files.slice(0, 5)) {
|
|
1076
|
+
try {
|
|
1077
|
+
const raw = readFileSync(file, "utf-8");
|
|
1078
|
+
const content = truncateToDepth(raw, depth);
|
|
1079
|
+
entries.push({
|
|
1080
|
+
layer: "domain",
|
|
1081
|
+
source: file.replace(projectRoot, ""),
|
|
1082
|
+
content,
|
|
1083
|
+
priority: 80,
|
|
1084
|
+
depth,
|
|
1085
|
+
tokenEstimate: estimateTokens(content),
|
|
1086
|
+
metadata: { domain, paradigm: structure.paradigm, path: file }
|
|
1087
|
+
});
|
|
1088
|
+
} catch {
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1100
1091
|
}
|
|
1092
|
+
return entries;
|
|
1101
1093
|
}
|
|
1102
1094
|
/**
|
|
1103
|
-
*
|
|
1095
|
+
* Load task-specific context
|
|
1104
1096
|
*/
|
|
1105
|
-
async
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1097
|
+
async loadTaskContext(task, depth = "L2") {
|
|
1098
|
+
if (!task)
|
|
1099
|
+
return [];
|
|
1100
|
+
const entries = [];
|
|
1101
|
+
if (task.description) {
|
|
1102
|
+
const content = truncateToDepth(task.description, depth);
|
|
1103
|
+
entries.push({
|
|
1104
|
+
layer: "task",
|
|
1105
|
+
source: "task-description",
|
|
1106
|
+
content,
|
|
1107
|
+
priority: 90,
|
|
1108
|
+
depth,
|
|
1109
|
+
tokenEstimate: estimateTokens(content),
|
|
1110
|
+
metadata: { taskId: task.id }
|
|
1111
|
+
});
|
|
1110
1112
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1113
|
+
if (task.input && depth !== "L0") {
|
|
1114
|
+
const content = truncateToDepth(JSON.stringify(task.input, null, 2), depth);
|
|
1115
|
+
entries.push({
|
|
1116
|
+
layer: "task",
|
|
1117
|
+
source: "task-input",
|
|
1118
|
+
content,
|
|
1119
|
+
priority: 85,
|
|
1120
|
+
depth,
|
|
1121
|
+
tokenEstimate: estimateTokens(content),
|
|
1122
|
+
metadata: { taskId: task.id }
|
|
1123
|
+
});
|
|
1114
1124
|
}
|
|
1115
|
-
|
|
1116
|
-
task.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1117
|
-
convoy.cancelledTasks++;
|
|
1118
|
-
this.updateProgress(convoy);
|
|
1119
|
-
await this.skipDependentTasks(convoy, taskId);
|
|
1120
|
-
this.checkConvoyCompletion(convoy);
|
|
1121
|
-
await this.persistConvoy(convoy);
|
|
1122
|
-
this.emit("task:cancelled", convoy, task);
|
|
1125
|
+
return entries;
|
|
1123
1126
|
}
|
|
1124
1127
|
/**
|
|
1125
|
-
*
|
|
1128
|
+
* Load execution-specific context
|
|
1129
|
+
* - Recent errors
|
|
1130
|
+
* - Previous attempts
|
|
1131
|
+
* - Related task outputs
|
|
1126
1132
|
*/
|
|
1127
|
-
async
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1133
|
+
async loadExecutionContext(task, depth = "L2") {
|
|
1134
|
+
if (!task)
|
|
1135
|
+
return [];
|
|
1136
|
+
const entries = [];
|
|
1137
|
+
if (task.output && depth !== "L0") {
|
|
1138
|
+
const content = truncateToDepth(JSON.stringify(task.output, null, 2), depth);
|
|
1139
|
+
entries.push({
|
|
1140
|
+
layer: "execution",
|
|
1141
|
+
source: "previous-output",
|
|
1142
|
+
content,
|
|
1143
|
+
priority: 70,
|
|
1144
|
+
depth,
|
|
1145
|
+
tokenEstimate: estimateTokens(content),
|
|
1146
|
+
metadata: { taskId: task.id }
|
|
1147
|
+
});
|
|
1135
1148
|
}
|
|
1136
|
-
|
|
1137
|
-
await this.persistConvoy(convoy);
|
|
1138
|
-
this.emit("convoy:paused", convoy);
|
|
1149
|
+
return entries;
|
|
1139
1150
|
}
|
|
1140
1151
|
/**
|
|
1141
|
-
*
|
|
1152
|
+
* Infer domain from task
|
|
1142
1153
|
*/
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1154
|
+
inferDomain(task) {
|
|
1155
|
+
const text = `${task.name} ${task.description || ""}`.toLowerCase();
|
|
1156
|
+
const domains = {
|
|
1157
|
+
api: ["api", "endpoint", "route", "controller"],
|
|
1158
|
+
ui: ["ui", "component", "view", "page", "frontend"],
|
|
1159
|
+
database: ["database", "db", "schema", "migration", "model"],
|
|
1160
|
+
auth: ["auth", "login", "user", "permission", "session"],
|
|
1161
|
+
test: ["test", "spec", "e2e", "unit", "integration"],
|
|
1162
|
+
config: ["config", "settings", "env", "setup"]
|
|
1163
|
+
};
|
|
1164
|
+
for (const [domain, keywords] of Object.entries(domains)) {
|
|
1165
|
+
if (keywords.some((kw) => text.includes(kw))) {
|
|
1166
|
+
return domain;
|
|
1167
|
+
}
|
|
1151
1168
|
}
|
|
1152
|
-
|
|
1153
|
-
await this.persistConvoy(convoy);
|
|
1154
|
-
this.emit("convoy:resumed", convoy);
|
|
1169
|
+
return null;
|
|
1155
1170
|
}
|
|
1156
1171
|
/**
|
|
1157
|
-
*
|
|
1172
|
+
* Get file patterns for a domain (paradigm-aware)
|
|
1158
1173
|
*/
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1174
|
+
getDomainPatterns(domain, paradigm) {
|
|
1175
|
+
const basePatterns = {
|
|
1176
|
+
api: ["**/api/**/*.ts", "**/routes/**/*.ts", "**/controllers/**/*.ts"],
|
|
1177
|
+
ui: ["**/components/**/*.{tsx,vue}", "**/pages/**/*.{tsx,vue}", "**/views/**/*.{tsx,vue}"],
|
|
1178
|
+
database: ["**/models/**/*.ts", "**/schema/**/*.ts", "**/migrations/**/*.ts"],
|
|
1179
|
+
auth: ["**/auth/**/*.ts", "**/middleware/auth*.ts"],
|
|
1180
|
+
test: ["**/*.test.ts", "**/*.spec.ts"],
|
|
1181
|
+
config: ["**/config/**/*.ts", "**/*.config.ts"]
|
|
1182
|
+
};
|
|
1183
|
+
let patterns = basePatterns[domain] || [];
|
|
1184
|
+
if (paradigm === "monorepo") {
|
|
1185
|
+
patterns = patterns.map((p) => `packages/*/${p}`);
|
|
1186
|
+
} else if (paradigm === "fullstack") {
|
|
1187
|
+
if (domain === "ui") {
|
|
1188
|
+
patterns = patterns.map((p) => `client/${p}`);
|
|
1189
|
+
} else if (domain === "api") {
|
|
1190
|
+
patterns = patterns.map((p) => `server/${p}`);
|
|
1172
1191
|
}
|
|
1173
1192
|
}
|
|
1174
|
-
|
|
1175
|
-
await this.persistConvoy(convoy);
|
|
1176
|
-
this.emit("convoy:cancelled", convoy);
|
|
1193
|
+
return patterns;
|
|
1177
1194
|
}
|
|
1178
1195
|
/**
|
|
1179
|
-
* Get
|
|
1196
|
+
* Get cache key
|
|
1180
1197
|
*/
|
|
1181
|
-
|
|
1182
|
-
return
|
|
1198
|
+
getCacheKey(projectRoot, layers, taskId, depth = "L2") {
|
|
1199
|
+
return `${projectRoot}:${layers.join(",")}:${taskId || "none"}:${depth}`;
|
|
1183
1200
|
}
|
|
1184
1201
|
/**
|
|
1185
|
-
*
|
|
1202
|
+
* Clear cache
|
|
1186
1203
|
*/
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (filter) {
|
|
1190
|
-
if (filter.status) {
|
|
1191
|
-
convoys = convoys.filter((c) => c.status === filter.status);
|
|
1192
|
-
}
|
|
1193
|
-
if (filter.createdBy) {
|
|
1194
|
-
convoys = convoys.filter((c) => c.createdBy === filter.createdBy);
|
|
1195
|
-
}
|
|
1196
|
-
if (filter.tags && filter.tags.length > 0) {
|
|
1197
|
-
convoys = convoys.filter(
|
|
1198
|
-
(c) => filter.tags.some((tag) => c.tags.includes(tag))
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
return convoys.sort(
|
|
1203
|
-
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
1204
|
-
);
|
|
1204
|
+
clearCache() {
|
|
1205
|
+
this.cache.clear();
|
|
1205
1206
|
}
|
|
1206
1207
|
/**
|
|
1207
|
-
*
|
|
1208
|
+
* Format context for LLM consumption
|
|
1208
1209
|
*/
|
|
1209
|
-
|
|
1210
|
-
|
|
1210
|
+
formatForLLM(context) {
|
|
1211
|
+
const sections = [];
|
|
1212
|
+
sections.push(`<!-- context depth=${context.depth} tokens\u2248${context.totalTokens} -->
|
|
1213
|
+
`);
|
|
1214
|
+
const layerOrder = ["project", "domain", "task", "execution"];
|
|
1215
|
+
for (const layer of layerOrder) {
|
|
1216
|
+
const entries = context.layers.get(layer);
|
|
1217
|
+
if (!entries || entries.length === 0)
|
|
1218
|
+
continue;
|
|
1219
|
+
sections.push(`# ${layer.toUpperCase()} CONTEXT
|
|
1220
|
+
`);
|
|
1221
|
+
const sorted = entries.sort((a, b) => b.priority - a.priority);
|
|
1222
|
+
for (const entry of sorted) {
|
|
1223
|
+
sections.push(`## ${entry.source} [${entry.depth}]
|
|
1224
|
+
`);
|
|
1225
|
+
sections.push(entry.content);
|
|
1226
|
+
sections.push("\n");
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
return sections.join("\n");
|
|
1211
1230
|
}
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1231
|
+
}
|
|
1232
|
+
const contextLoader = new ContextLoader();
|
|
1233
|
+
async function loadContextAtDepth(depth, options = {}) {
|
|
1234
|
+
return contextLoader.load({ ...options, depth });
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
const contextLoader$1 = {
|
|
1238
|
+
__proto__: null,
|
|
1239
|
+
ContextLoader: ContextLoader,
|
|
1240
|
+
contextLoader: contextLoader,
|
|
1241
|
+
loadContextAtDepth: loadContextAtDepth
|
|
1242
|
+
};
|
|
1243
|
+
|
|
1244
|
+
function detectTaskPhase(history) {
|
|
1245
|
+
if (history.length === 0)
|
|
1246
|
+
return "idle";
|
|
1247
|
+
const recent = history.slice(-10);
|
|
1248
|
+
const recentText = recent.map((h) => h.content).join(" ").toLowerCase();
|
|
1249
|
+
if (recentText.includes("write") || recentText.includes("create") || recentText.includes("generating"))
|
|
1250
|
+
return "generating";
|
|
1251
|
+
if (recentText.includes("test") || recentText.includes("review") || recentText.includes("check"))
|
|
1252
|
+
return "reviewing";
|
|
1253
|
+
if (recentText.includes("edit") || recentText.includes("bash") || recentText.includes("run"))
|
|
1254
|
+
return "executing";
|
|
1255
|
+
if (recentText.includes("read") || recentText.includes("search") || recentText.includes("grep"))
|
|
1256
|
+
return "exploring";
|
|
1257
|
+
return "idle";
|
|
1258
|
+
}
|
|
1259
|
+
function extractFilePaths(content) {
|
|
1260
|
+
const matches = content.match(/\/[\w./\-]+\.\w+|[\w./\-]+\.(?:ts|js|json|md|py|go|rs)/g);
|
|
1261
|
+
return matches ? [...new Set(matches)] : [];
|
|
1262
|
+
}
|
|
1263
|
+
class SessionIntelligence {
|
|
1264
|
+
static instance = null;
|
|
1265
|
+
session;
|
|
1266
|
+
constructor(session) {
|
|
1267
|
+
this.session = session;
|
|
1268
|
+
if (!session.intelligence) {
|
|
1269
|
+
session.intelligence = {
|
|
1270
|
+
taskPhase: "idle",
|
|
1271
|
+
filesTouched: [],
|
|
1272
|
+
keyDecisions: [],
|
|
1273
|
+
toolCallCount: 0,
|
|
1274
|
+
compressionWatermark: 0
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
get state() {
|
|
1279
|
+
return this.session.intelligence;
|
|
1224
1280
|
}
|
|
1225
1281
|
/**
|
|
1226
|
-
*
|
|
1282
|
+
* Observe a new history entry and update intelligence state.
|
|
1283
|
+
* Call this after every message added to the session.
|
|
1227
1284
|
*/
|
|
1228
|
-
|
|
1229
|
-
const
|
|
1230
|
-
if (!
|
|
1231
|
-
|
|
1232
|
-
const lines = [
|
|
1233
|
-
`Convoy: ${convoy.name} (${convoy.id})`,
|
|
1234
|
-
`Status: ${convoy.status}`,
|
|
1235
|
-
`Progress: ${convoy.progress}%`,
|
|
1236
|
-
`Tasks: ${convoy.completedTasks}/${convoy.totalTasks} completed`
|
|
1237
|
-
];
|
|
1238
|
-
if (convoy.failedTasks > 0) {
|
|
1239
|
-
lines.push(`Failed: ${convoy.failedTasks}`);
|
|
1285
|
+
observe(entry) {
|
|
1286
|
+
const intel = this.state;
|
|
1287
|
+
if (!intel.userOriginalIntent && entry.role === "user" && entry.content.trim().length > 10) {
|
|
1288
|
+
intel.userOriginalIntent = entry.content.slice(0, 500);
|
|
1240
1289
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1290
|
+
const files = extractFilePaths(entry.content);
|
|
1291
|
+
for (const f of files) {
|
|
1292
|
+
if (!intel.filesTouched.includes(f)) {
|
|
1293
|
+
intel.filesTouched.push(f);
|
|
1294
|
+
}
|
|
1243
1295
|
}
|
|
1244
|
-
if (
|
|
1245
|
-
|
|
1296
|
+
if (entry.role === "assistant" && /\b(bash|read|write|edit|grep|glob)\b/i.test(entry.content)) {
|
|
1297
|
+
intel.toolCallCount++;
|
|
1246
1298
|
}
|
|
1247
|
-
|
|
1299
|
+
intel.taskPhase = detectTaskPhase(this.session.history);
|
|
1248
1300
|
}
|
|
1249
1301
|
/**
|
|
1250
|
-
*
|
|
1302
|
+
* Record a key decision explicitly.
|
|
1251
1303
|
*/
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
return true;
|
|
1259
|
-
}
|
|
1260
|
-
// ========================================================================
|
|
1261
|
-
// Private Methods
|
|
1262
|
-
// ========================================================================
|
|
1263
|
-
async ensureInitialized() {
|
|
1264
|
-
if (!this.initialized) {
|
|
1265
|
-
await this.initialize();
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
updateProgress(convoy) {
|
|
1269
|
-
const processed = convoy.completedTasks + convoy.failedTasks + convoy.cancelledTasks + convoy.skippedTasks;
|
|
1270
|
-
convoy.progress = convoy.totalTasks > 0 ? Math.round(processed / convoy.totalTasks * 100) : 0;
|
|
1271
|
-
}
|
|
1272
|
-
checkConvoyCompletion(convoy) {
|
|
1273
|
-
const processed = convoy.completedTasks + convoy.failedTasks + convoy.cancelledTasks + convoy.skippedTasks;
|
|
1274
|
-
if (processed === convoy.totalTasks) {
|
|
1275
|
-
convoy.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1276
|
-
if (convoy.failedTasks === 0 && convoy.cancelledTasks === 0) {
|
|
1277
|
-
convoy.status = "completed";
|
|
1278
|
-
if (convoy.notifications.onComplete) {
|
|
1279
|
-
this.emit("convoy:completed", convoy);
|
|
1280
|
-
}
|
|
1281
|
-
} else {
|
|
1282
|
-
convoy.status = "failed";
|
|
1283
|
-
if (convoy.notifications.onFailure) {
|
|
1284
|
-
this.emit("convoy:failed", convoy);
|
|
1285
|
-
}
|
|
1304
|
+
recordDecision(decision) {
|
|
1305
|
+
const intel = this.state;
|
|
1306
|
+
if (!intel.keyDecisions.includes(decision)) {
|
|
1307
|
+
intel.keyDecisions.push(decision.slice(0, 200));
|
|
1308
|
+
if (intel.keyDecisions.length > 20) {
|
|
1309
|
+
intel.keyDecisions.shift();
|
|
1286
1310
|
}
|
|
1287
1311
|
}
|
|
1288
1312
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1313
|
+
/**
|
|
1314
|
+
* Mark that history up to index N has been compressed.
|
|
1315
|
+
*/
|
|
1316
|
+
markCompressed(upToIndex) {
|
|
1317
|
+
this.state.compressionWatermark = upToIndex;
|
|
1294
1318
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1319
|
+
/**
|
|
1320
|
+
* Generate a system prompt injection that preserves intent across compaction.
|
|
1321
|
+
* Inject this whenever context is about to be compressed.
|
|
1322
|
+
*/
|
|
1323
|
+
getSystemPromptInjection() {
|
|
1324
|
+
const intel = this.state;
|
|
1325
|
+
const parts = ["<!-- session-intelligence -->"];
|
|
1326
|
+
if (intel.userOriginalIntent) {
|
|
1327
|
+
parts.push(`Original user intent: ${intel.userOriginalIntent}`);
|
|
1328
|
+
}
|
|
1329
|
+
if (intel.keyDecisions.length > 0) {
|
|
1330
|
+
parts.push(`Key decisions: ${intel.keyDecisions.join("; ")}`);
|
|
1331
|
+
}
|
|
1332
|
+
if (intel.filesTouched.length > 0) {
|
|
1333
|
+
parts.push(`Files touched: ${intel.filesTouched.slice(-20).join(", ")}`);
|
|
1305
1334
|
}
|
|
1335
|
+
parts.push(`Task phase: ${intel.taskPhase} | Tool calls: ${intel.toolCallCount}`);
|
|
1336
|
+
parts.push("<!-- /session-intelligence -->");
|
|
1337
|
+
return parts.join("\n");
|
|
1306
1338
|
}
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1339
|
+
/**
|
|
1340
|
+
* Returns true if the session has enough context to be worth preserving.
|
|
1341
|
+
*/
|
|
1342
|
+
hasSignificantContext() {
|
|
1343
|
+
const intel = this.state;
|
|
1344
|
+
return !!intel.userOriginalIntent || intel.toolCallCount > 3 || intel.filesTouched.length > 0;
|
|
1345
|
+
}
|
|
1346
|
+
static getInstance() {
|
|
1347
|
+
if (!SessionIntelligence.instance) {
|
|
1348
|
+
SessionIntelligence.instance = new SessionIntelligence({
|
|
1349
|
+
id: "session-intelligence-singleton",
|
|
1350
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
1351
|
+
lastUsedAt: /* @__PURE__ */ new Date(),
|
|
1352
|
+
history: []
|
|
1353
|
+
});
|
|
1316
1354
|
}
|
|
1355
|
+
return SessionIntelligence.instance;
|
|
1317
1356
|
}
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1357
|
+
recordMessage(role, content) {
|
|
1358
|
+
const entry = {
|
|
1359
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
1360
|
+
role,
|
|
1361
|
+
content
|
|
1362
|
+
};
|
|
1363
|
+
this.session.history.push(entry);
|
|
1364
|
+
this.observe(entry);
|
|
1321
1365
|
}
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
function getGlobalConvoyManager() {
|
|
1325
|
-
if (!globalConvoyManager) {
|
|
1326
|
-
globalConvoyManager = new ConvoyManager();
|
|
1366
|
+
updatePhase(phase) {
|
|
1367
|
+
this.state.taskPhase = phase;
|
|
1327
1368
|
}
|
|
1328
|
-
return globalConvoyManager;
|
|
1329
1369
|
}
|
|
1330
1370
|
|
|
1331
|
-
|
|
1332
|
-
__proto__: null,
|
|
1333
|
-
ConvoyManager: ConvoyManager,
|
|
1334
|
-
getGlobalConvoyManager: getGlobalConvoyManager
|
|
1335
|
-
};
|
|
1336
|
-
|
|
1337
|
-
export { SessionIntelligence as S, getGlobalStateManager as a, contextLoader$1 as b, contextLoader as c, gitBackedState as d, convoyManager as e, getGlobalConvoyManager as g };
|
|
1371
|
+
export { SessionIntelligence as S, getGlobalStateManager as a, gitBackedState as b, contextLoader as c, convoyManager as d, contextLoader$1 as e, getGlobalConvoyManager as g };
|