@askexenow/exe-os 0.8.0 → 0.8.1
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/README.md +178 -79
- package/dist/bin/backfill-responses.js +160 -8
- package/dist/bin/backfill-vectors.js +130 -1
- package/dist/bin/cleanup-stale-review-tasks.js +130 -1
- package/dist/bin/cli.js +10111 -7540
- package/dist/bin/exe-agent.js +159 -1
- package/dist/bin/exe-assign.js +235 -16
- package/dist/bin/exe-boot.js +344 -472
- package/dist/bin/exe-call.js +145 -1
- package/dist/bin/exe-cloud.js +11 -0
- package/dist/bin/exe-dispatch.js +37 -24
- package/dist/bin/exe-doctor.js +130 -1
- package/dist/bin/exe-export-behaviors.js +150 -7
- package/dist/bin/exe-forget.js +822 -665
- package/dist/bin/exe-gateway.js +470 -62
- package/dist/bin/exe-heartbeat.js +133 -2
- package/dist/bin/exe-kill.js +150 -7
- package/dist/bin/exe-launch-agent.js +150 -7
- package/dist/bin/exe-new-employee.js +756 -224
- package/dist/bin/exe-pending-messages.js +132 -2
- package/dist/bin/exe-pending-notifications.js +130 -1
- package/dist/bin/exe-pending-reviews.js +132 -2
- package/dist/bin/exe-review.js +160 -8
- package/dist/bin/exe-search.js +2473 -2008
- package/dist/bin/exe-session-cleanup.js +238 -51
- package/dist/bin/exe-settings.js +11 -0
- package/dist/bin/exe-status.js +130 -1
- package/dist/bin/exe-team.js +130 -1
- package/dist/bin/git-sweep.js +272 -16
- package/dist/bin/graph-backfill.js +150 -7
- package/dist/bin/graph-export.js +150 -7
- package/dist/bin/install.js +5 -0
- package/dist/bin/scan-tasks.js +238 -19
- package/dist/bin/setup.js +1776 -10
- package/dist/bin/shard-migrate.js +150 -7
- package/dist/bin/update.js +9 -6
- package/dist/bin/wiki-sync.js +150 -7
- package/dist/gateway/index.js +470 -62
- package/dist/hooks/bug-report-worker.js +195 -35
- package/dist/hooks/commit-complete.js +272 -16
- package/dist/hooks/error-recall.js +2313 -1847
- package/dist/hooks/exe-heartbeat-hook.js +5 -0
- package/dist/hooks/ingest-worker.js +330 -58
- package/dist/hooks/ingest.js +11 -0
- package/dist/hooks/instructions-loaded.js +199 -10
- package/dist/hooks/notification.js +199 -10
- package/dist/hooks/post-compact.js +199 -10
- package/dist/hooks/pre-compact.js +199 -10
- package/dist/hooks/pre-tool-use.js +199 -10
- package/dist/hooks/prompt-ingest-worker.js +179 -14
- package/dist/hooks/prompt-submit.js +781 -285
- package/dist/hooks/response-ingest-worker.js +1900 -1405
- package/dist/hooks/session-end.js +456 -12
- package/dist/hooks/session-start.js +2188 -1724
- package/dist/hooks/stop.js +200 -10
- package/dist/hooks/subagent-stop.js +199 -10
- package/dist/hooks/summary-worker.js +604 -334
- package/dist/index.js +554 -61
- package/dist/lib/cloud-sync.js +5 -0
- package/dist/lib/config.js +13 -0
- package/dist/lib/consolidation.js +5 -0
- package/dist/lib/database.js +104 -0
- package/dist/lib/device-registry.js +109 -0
- package/dist/lib/embedder.js +13 -0
- package/dist/lib/employee-templates.js +53 -26
- package/dist/lib/employees.js +5 -0
- package/dist/lib/exe-daemon-client.js +5 -0
- package/dist/lib/exe-daemon.js +493 -79
- package/dist/lib/file-grep.js +20 -4
- package/dist/lib/hybrid-search.js +1435 -190
- package/dist/lib/identity-templates.js +126 -5
- package/dist/lib/identity.js +5 -0
- package/dist/lib/license.js +5 -0
- package/dist/lib/messaging.js +37 -24
- package/dist/lib/schedules.js +130 -1
- package/dist/lib/skill-learning.js +11 -0
- package/dist/lib/status-brief.js +5 -0
- package/dist/lib/store.js +199 -10
- package/dist/lib/task-router.js +72 -6
- package/dist/lib/tasks.js +179 -50
- package/dist/lib/tmux-routing.js +179 -46
- package/dist/mcp/server.js +2129 -1855
- package/dist/mcp/tools/create-task.js +86 -36
- package/dist/mcp/tools/deactivate-behavior.js +5 -0
- package/dist/mcp/tools/list-tasks.js +39 -11
- package/dist/mcp/tools/send-message.js +37 -24
- package/dist/mcp/tools/update-task.js +153 -38
- package/dist/runtime/index.js +451 -59
- package/dist/tui/App.js +454 -59
- package/package.json +1 -1
package/dist/tui/App.js
CHANGED
|
@@ -260,6 +260,27 @@ async function ensureSchema() {
|
|
|
260
260
|
});
|
|
261
261
|
} catch {
|
|
262
262
|
}
|
|
263
|
+
try {
|
|
264
|
+
await client.execute({
|
|
265
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint TEXT`,
|
|
266
|
+
args: []
|
|
267
|
+
});
|
|
268
|
+
} catch {
|
|
269
|
+
}
|
|
270
|
+
try {
|
|
271
|
+
await client.execute({
|
|
272
|
+
sql: `ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER NOT NULL DEFAULT 0`,
|
|
273
|
+
args: []
|
|
274
|
+
});
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
await client.execute({
|
|
279
|
+
sql: `ALTER TABLE tasks ADD COLUMN complexity TEXT NOT NULL DEFAULT 'standard'`,
|
|
280
|
+
args: []
|
|
281
|
+
});
|
|
282
|
+
} catch {
|
|
283
|
+
}
|
|
263
284
|
try {
|
|
264
285
|
await client.execute({
|
|
265
286
|
sql: `ALTER TABLE memories ADD COLUMN task_id TEXT`,
|
|
@@ -670,6 +691,15 @@ async function ensureSchema() {
|
|
|
670
691
|
} catch {
|
|
671
692
|
}
|
|
672
693
|
}
|
|
694
|
+
for (const col of [
|
|
695
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
696
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'"
|
|
697
|
+
]) {
|
|
698
|
+
try {
|
|
699
|
+
await client.execute(col);
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
673
703
|
await client.executeMultiple(`
|
|
674
704
|
CREATE INDEX IF NOT EXISTS idx_memories_workspace
|
|
675
705
|
ON memories(workspace_id);
|
|
@@ -734,6 +764,34 @@ async function ensureSchema() {
|
|
|
734
764
|
CREATE INDEX IF NOT EXISTS idx_conversations_channel
|
|
735
765
|
ON conversations(channel_id);
|
|
736
766
|
`);
|
|
767
|
+
try {
|
|
768
|
+
await client.execute({
|
|
769
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_tokens INTEGER`,
|
|
770
|
+
args: []
|
|
771
|
+
});
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
await client.execute({
|
|
776
|
+
sql: `ALTER TABLE tasks ADD COLUMN budget_fallback_model TEXT`,
|
|
777
|
+
args: []
|
|
778
|
+
});
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
try {
|
|
782
|
+
await client.execute({
|
|
783
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_used INTEGER DEFAULT 0`,
|
|
784
|
+
args: []
|
|
785
|
+
});
|
|
786
|
+
} catch {
|
|
787
|
+
}
|
|
788
|
+
try {
|
|
789
|
+
await client.execute({
|
|
790
|
+
sql: `ALTER TABLE tasks ADD COLUMN tokens_warned_at INTEGER`,
|
|
791
|
+
args: []
|
|
792
|
+
});
|
|
793
|
+
} catch {
|
|
794
|
+
}
|
|
737
795
|
await client.executeMultiple(`
|
|
738
796
|
CREATE VIRTUAL TABLE IF NOT EXISTS conversations_fts USING fts5(
|
|
739
797
|
content_text,
|
|
@@ -760,6 +818,52 @@ async function ensureSchema() {
|
|
|
760
818
|
VALUES (new.rowid, new.content_text, new.sender_name, new.agent_response);
|
|
761
819
|
END;
|
|
762
820
|
`);
|
|
821
|
+
try {
|
|
822
|
+
await client.execute({
|
|
823
|
+
sql: `ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3`,
|
|
824
|
+
args: []
|
|
825
|
+
});
|
|
826
|
+
} catch {
|
|
827
|
+
}
|
|
828
|
+
try {
|
|
829
|
+
await client.execute(
|
|
830
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)`
|
|
831
|
+
);
|
|
832
|
+
} catch {
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
await client.execute({
|
|
836
|
+
sql: `UPDATE memories SET tier = 1 WHERE tool_name = 'commit_to_long_term_memory' AND importance >= 8 AND tier = 3`,
|
|
837
|
+
args: []
|
|
838
|
+
});
|
|
839
|
+
await client.execute({
|
|
840
|
+
sql: `UPDATE memories SET tier = 2 WHERE tool_name IN ('store_memory', 'manual') AND importance >= 5 AND tier = 3`,
|
|
841
|
+
args: []
|
|
842
|
+
});
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
try {
|
|
846
|
+
await client.execute({
|
|
847
|
+
sql: `ALTER TABLE memories ADD COLUMN supersedes_id TEXT`,
|
|
848
|
+
args: []
|
|
849
|
+
});
|
|
850
|
+
} catch {
|
|
851
|
+
}
|
|
852
|
+
try {
|
|
853
|
+
await client.execute(
|
|
854
|
+
`CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL`
|
|
855
|
+
);
|
|
856
|
+
} catch {
|
|
857
|
+
}
|
|
858
|
+
for (const col of [
|
|
859
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint TEXT",
|
|
860
|
+
"ALTER TABLE tasks ADD COLUMN checkpoint_count INTEGER DEFAULT 0"
|
|
861
|
+
]) {
|
|
862
|
+
try {
|
|
863
|
+
await client.execute(col);
|
|
864
|
+
} catch {
|
|
865
|
+
}
|
|
866
|
+
}
|
|
763
867
|
}
|
|
764
868
|
async function disposeDatabase() {
|
|
765
869
|
if (_client) {
|
|
@@ -858,6 +962,11 @@ function normalizeSessionLifecycle(raw) {
|
|
|
858
962
|
const userSL = raw.sessionLifecycle ?? {};
|
|
859
963
|
raw.sessionLifecycle = { ...defaultSL, ...userSL };
|
|
860
964
|
}
|
|
965
|
+
function normalizeAutoUpdate(raw) {
|
|
966
|
+
const defaultAU = DEFAULT_CONFIG.autoUpdate;
|
|
967
|
+
const userAU = raw.autoUpdate ?? {};
|
|
968
|
+
raw.autoUpdate = { ...defaultAU, ...userAU };
|
|
969
|
+
}
|
|
861
970
|
async function loadConfig() {
|
|
862
971
|
const dir = process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? EXE_AI_DIR;
|
|
863
972
|
await mkdir(dir, { recursive: true });
|
|
@@ -880,6 +989,7 @@ async function loadConfig() {
|
|
|
880
989
|
}
|
|
881
990
|
normalizeScalingRoadmap(migratedCfg);
|
|
882
991
|
normalizeSessionLifecycle(migratedCfg);
|
|
992
|
+
normalizeAutoUpdate(migratedCfg);
|
|
883
993
|
const config = { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
884
994
|
if (config.dbPath.startsWith("~")) {
|
|
885
995
|
config.dbPath = config.dbPath.replace(/^~/, os.homedir());
|
|
@@ -902,6 +1012,7 @@ function loadConfigSync() {
|
|
|
902
1012
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
903
1013
|
normalizeScalingRoadmap(migratedCfg);
|
|
904
1014
|
normalizeSessionLifecycle(migratedCfg);
|
|
1015
|
+
normalizeAutoUpdate(migratedCfg);
|
|
905
1016
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db"), ...migratedCfg };
|
|
906
1017
|
} catch {
|
|
907
1018
|
return { ...DEFAULT_CONFIG, dbPath: path.join(dir, "memories.db") };
|
|
@@ -921,6 +1032,7 @@ async function loadConfigFrom(configPath) {
|
|
|
921
1032
|
const { config: migratedCfg } = migrateConfig(parsed);
|
|
922
1033
|
normalizeScalingRoadmap(migratedCfg);
|
|
923
1034
|
normalizeSessionLifecycle(migratedCfg);
|
|
1035
|
+
normalizeAutoUpdate(migratedCfg);
|
|
924
1036
|
return { ...DEFAULT_CONFIG, ...migratedCfg };
|
|
925
1037
|
} catch {
|
|
926
1038
|
return { ...DEFAULT_CONFIG };
|
|
@@ -992,6 +1104,11 @@ var init_config = __esm({
|
|
|
992
1104
|
idleKillTicksRequired: 3,
|
|
993
1105
|
idleKillIntercomAckWindowMs: 1e4,
|
|
994
1106
|
maxAutoInstances: 10
|
|
1107
|
+
},
|
|
1108
|
+
autoUpdate: {
|
|
1109
|
+
checkOnBoot: true,
|
|
1110
|
+
autoInstall: false,
|
|
1111
|
+
checkIntervalMs: 24 * 60 * 60 * 1e3
|
|
995
1112
|
}
|
|
996
1113
|
};
|
|
997
1114
|
CONFIG_MIGRATIONS = [
|
|
@@ -2259,6 +2376,25 @@ async function* agentLoop(userMessage, history, config) {
|
|
|
2259
2376
|
}
|
|
2260
2377
|
totalUsage.inputTokens += response.usage.inputTokens;
|
|
2261
2378
|
totalUsage.outputTokens += response.usage.outputTokens;
|
|
2379
|
+
if (config.tokenBudgetMiddleware) {
|
|
2380
|
+
const result = await config.tokenBudgetMiddleware.onTokenUsed(
|
|
2381
|
+
response.usage.inputTokens,
|
|
2382
|
+
response.usage.outputTokens
|
|
2383
|
+
);
|
|
2384
|
+
if (result.warned && config.hooks.onNotification) {
|
|
2385
|
+
await config.hooks.onNotification(
|
|
2386
|
+
`\u26A0\uFE0F Token budget at ${result.percentUsed}%. Fallback model: ${result.fallback ?? "none (task will terminate at 100%)"}.`
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
if (result.exceeded) {
|
|
2390
|
+
if (config.hooks.onTokenBudgetExceeded) {
|
|
2391
|
+
await config.hooks.onTokenBudgetExceeded(context, result.fallback);
|
|
2392
|
+
}
|
|
2393
|
+
abortController.abort();
|
|
2394
|
+
yield { type: "error", error: new Error("Token budget exceeded. Task requires manual continuation.") };
|
|
2395
|
+
break;
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2262
2398
|
contextManager.updateFromApiUsage(response.usage.inputTokens, response.usage.outputTokens);
|
|
2263
2399
|
contextManager.updateFromMessages(messages);
|
|
2264
2400
|
await contextManager.checkPressure();
|
|
@@ -2857,6 +2993,11 @@ function composeHooks(...pipelines) {
|
|
|
2857
2993
|
for (const p of pipelines) {
|
|
2858
2994
|
if (p.onCrossAgentMessage) await p.onCrossAgentMessage(event);
|
|
2859
2995
|
}
|
|
2996
|
+
},
|
|
2997
|
+
async onTokenBudgetExceeded(ctx, fallback) {
|
|
2998
|
+
for (const p of pipelines) {
|
|
2999
|
+
if (p.onTokenBudgetExceeded) await p.onTokenBudgetExceeded(ctx, fallback);
|
|
3000
|
+
}
|
|
2860
3001
|
}
|
|
2861
3002
|
};
|
|
2862
3003
|
}
|
|
@@ -3708,6 +3849,16 @@ var init_employees = __esm({
|
|
|
3708
3849
|
|
|
3709
3850
|
// src/lib/task-router.ts
|
|
3710
3851
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3852
|
+
function resolveBloomRouting(complexity, config = DEFAULT_BLOOM_CONFIG) {
|
|
3853
|
+
const tier = config.complexityToTier[complexity];
|
|
3854
|
+
const rule = config.tierRules[tier];
|
|
3855
|
+
return {
|
|
3856
|
+
tier,
|
|
3857
|
+
reviewRequired: rule.reviewRequired,
|
|
3858
|
+
manualOnly: rule.manualOnly,
|
|
3859
|
+
eligible: rule.eligible
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3711
3862
|
async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
3712
3863
|
const results = await searchFn(taskVector, agentId, { limit: 5 });
|
|
3713
3864
|
if (results.length === 0) {
|
|
@@ -3720,13 +3871,29 @@ async function scoreEmployee(taskVector, agentId, searchFn) {
|
|
|
3720
3871
|
}
|
|
3721
3872
|
return { agentId, score: results.length / 5 };
|
|
3722
3873
|
}
|
|
3723
|
-
async function routeTask(taskDescription, employees, embedFn, searchFn) {
|
|
3724
|
-
|
|
3874
|
+
async function routeTask(taskDescription, employees, embedFn, searchFn, options) {
|
|
3875
|
+
let specialists = employees.filter((e) => e.name !== "exe");
|
|
3725
3876
|
if (specialists.length === 0) {
|
|
3726
3877
|
throw new Error(
|
|
3727
3878
|
"No specialist employees available. Create one with /exe-new-employee."
|
|
3728
3879
|
);
|
|
3729
3880
|
}
|
|
3881
|
+
let bloomRouting;
|
|
3882
|
+
if (options?.complexity) {
|
|
3883
|
+
bloomRouting = resolveBloomRouting(options.complexity, options.bloomConfig);
|
|
3884
|
+
if (bloomRouting.manualOnly) {
|
|
3885
|
+
throw new Error(
|
|
3886
|
+
`Task complexity "${options.complexity}" requires manual assignment (tier: ${bloomRouting.tier}).`
|
|
3887
|
+
);
|
|
3888
|
+
}
|
|
3889
|
+
if (bloomRouting.eligible.length > 0) {
|
|
3890
|
+
const eligible = new Set(bloomRouting.eligible);
|
|
3891
|
+
const filtered = specialists.filter((e) => eligible.has(e.name));
|
|
3892
|
+
if (filtered.length > 0) {
|
|
3893
|
+
specialists = filtered;
|
|
3894
|
+
}
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3730
3897
|
const taskVector = await embedFn(taskDescription);
|
|
3731
3898
|
const scored = await Promise.all(
|
|
3732
3899
|
specialists.map(async (emp) => {
|
|
@@ -3735,11 +3902,43 @@ async function routeTask(taskDescription, employees, embedFn, searchFn) {
|
|
|
3735
3902
|
})
|
|
3736
3903
|
);
|
|
3737
3904
|
scored.sort((a, b) => b.score - a.score);
|
|
3738
|
-
return scored[0];
|
|
3905
|
+
return { ...scored[0], bloomRouting };
|
|
3739
3906
|
}
|
|
3907
|
+
var DEFAULT_BLOOM_CONFIG;
|
|
3740
3908
|
var init_task_router = __esm({
|
|
3741
3909
|
"src/lib/task-router.ts"() {
|
|
3742
3910
|
"use strict";
|
|
3911
|
+
DEFAULT_BLOOM_CONFIG = {
|
|
3912
|
+
complexityToTier: {
|
|
3913
|
+
routine: "junior",
|
|
3914
|
+
standard: "standard",
|
|
3915
|
+
complex: "senior",
|
|
3916
|
+
critical: "specialist"
|
|
3917
|
+
},
|
|
3918
|
+
tierRules: {
|
|
3919
|
+
junior: {
|
|
3920
|
+
eligible: ["tom"],
|
|
3921
|
+
reviewRequired: false,
|
|
3922
|
+
manualOnly: false
|
|
3923
|
+
},
|
|
3924
|
+
standard: {
|
|
3925
|
+
eligible: ["tom"],
|
|
3926
|
+
reviewRequired: false,
|
|
3927
|
+
manualOnly: false
|
|
3928
|
+
},
|
|
3929
|
+
senior: {
|
|
3930
|
+
eligible: [],
|
|
3931
|
+
// any specialist, but review required
|
|
3932
|
+
reviewRequired: true,
|
|
3933
|
+
manualOnly: false
|
|
3934
|
+
},
|
|
3935
|
+
specialist: {
|
|
3936
|
+
eligible: [],
|
|
3937
|
+
reviewRequired: true,
|
|
3938
|
+
manualOnly: true
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3743
3942
|
}
|
|
3744
3943
|
});
|
|
3745
3944
|
|
|
@@ -4005,11 +4204,12 @@ function queueIntercom(targetSession, reason) {
|
|
|
4005
4204
|
}
|
|
4006
4205
|
writeQueue(queue);
|
|
4007
4206
|
}
|
|
4008
|
-
var QUEUE_PATH, INTERCOM_LOG;
|
|
4207
|
+
var QUEUE_PATH, TTL_MS, INTERCOM_LOG;
|
|
4009
4208
|
var init_intercom_queue = __esm({
|
|
4010
4209
|
"src/lib/intercom-queue.ts"() {
|
|
4011
4210
|
"use strict";
|
|
4012
4211
|
QUEUE_PATH = path11.join(os4.homedir(), ".exe-os", "intercom-queue.json");
|
|
4212
|
+
TTL_MS = 60 * 60 * 1e3;
|
|
4013
4213
|
INTERCOM_LOG = path11.join(os4.homedir(), ".exe-os", "intercom.log");
|
|
4014
4214
|
}
|
|
4015
4215
|
});
|
|
@@ -4109,6 +4309,17 @@ function getGitRoot(dir) {
|
|
|
4109
4309
|
return null;
|
|
4110
4310
|
}
|
|
4111
4311
|
}
|
|
4312
|
+
function getMainRepoRoot(dir) {
|
|
4313
|
+
try {
|
|
4314
|
+
const commonDir = execSync6(
|
|
4315
|
+
"git rev-parse --path-format=absolute --git-common-dir",
|
|
4316
|
+
{ cwd: dir, encoding: "utf-8", timeout: GIT_TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] }
|
|
4317
|
+
).trim();
|
|
4318
|
+
return realpath(path13.dirname(commonDir));
|
|
4319
|
+
} catch {
|
|
4320
|
+
return null;
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4112
4323
|
function worktreePath(repoRoot, employeeName, instance) {
|
|
4113
4324
|
const label = instanceLabel(employeeName, instance);
|
|
4114
4325
|
return path13.join(repoRoot, ".worktrees", label);
|
|
@@ -4300,6 +4511,36 @@ import path15 from "path";
|
|
|
4300
4511
|
import { execSync as execSync7 } from "child_process";
|
|
4301
4512
|
import { mkdir as mkdir3, writeFile as writeFile3, appendFile } from "fs/promises";
|
|
4302
4513
|
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
4514
|
+
async function writeCheckpoint(input) {
|
|
4515
|
+
const client = getClient();
|
|
4516
|
+
const row = await resolveTask(client, input.taskId);
|
|
4517
|
+
const taskId = String(row.id);
|
|
4518
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4519
|
+
const blockedByIds = [];
|
|
4520
|
+
if (row.blocked_by) {
|
|
4521
|
+
blockedByIds.push(String(row.blocked_by));
|
|
4522
|
+
}
|
|
4523
|
+
const checkpoint = {
|
|
4524
|
+
step: input.step,
|
|
4525
|
+
context_summary: input.contextSummary,
|
|
4526
|
+
files_touched: input.filesTouched ?? [],
|
|
4527
|
+
blocked_by_ids: blockedByIds,
|
|
4528
|
+
last_checkpoint_at: now
|
|
4529
|
+
};
|
|
4530
|
+
const result = await client.execute({
|
|
4531
|
+
sql: `UPDATE tasks SET checkpoint = ?, checkpoint_count = checkpoint_count + 1, updated_at = ? WHERE id = ?`,
|
|
4532
|
+
args: [JSON.stringify(checkpoint), now, taskId]
|
|
4533
|
+
});
|
|
4534
|
+
if (result.rowsAffected === 0) {
|
|
4535
|
+
throw new Error(`Checkpoint write failed: task ${taskId} not found`);
|
|
4536
|
+
}
|
|
4537
|
+
const countResult = await client.execute({
|
|
4538
|
+
sql: "SELECT checkpoint_count FROM tasks WHERE id = ?",
|
|
4539
|
+
args: [taskId]
|
|
4540
|
+
});
|
|
4541
|
+
const checkpointCount = Number(countResult.rows[0]?.checkpoint_count ?? 1);
|
|
4542
|
+
return { checkpointCount };
|
|
4543
|
+
}
|
|
4303
4544
|
function extractParentFromContext(contextBody) {
|
|
4304
4545
|
if (!contextBody) return null;
|
|
4305
4546
|
const match = contextBody.match(
|
|
@@ -4406,9 +4647,10 @@ async function createTaskCore(input) {
|
|
|
4406
4647
|
} catch {
|
|
4407
4648
|
}
|
|
4408
4649
|
}
|
|
4650
|
+
const complexity = input.complexity ?? "standard";
|
|
4409
4651
|
await client.execute({
|
|
4410
|
-
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, created_at, updated_at)
|
|
4411
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4652
|
+
sql: `INSERT INTO tasks (id, title, assigned_to, assigned_by, project_name, priority, status, task_file, blocked_by, parent_task_id, reviewer, context, complexity, budget_tokens, budget_fallback_model, tokens_used, tokens_warned_at, created_at, updated_at)
|
|
4653
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
4412
4654
|
args: [
|
|
4413
4655
|
id,
|
|
4414
4656
|
input.title,
|
|
@@ -4422,6 +4664,11 @@ async function createTaskCore(input) {
|
|
|
4422
4664
|
parentTaskId,
|
|
4423
4665
|
input.reviewer ?? null,
|
|
4424
4666
|
input.context,
|
|
4667
|
+
input.budgetTokens ?? null,
|
|
4668
|
+
input.budgetFallbackModel ?? null,
|
|
4669
|
+
0,
|
|
4670
|
+
null,
|
|
4671
|
+
complexity,
|
|
4425
4672
|
now,
|
|
4426
4673
|
now
|
|
4427
4674
|
]
|
|
@@ -4437,7 +4684,11 @@ async function createTaskCore(input) {
|
|
|
4437
4684
|
taskFile,
|
|
4438
4685
|
createdAt: now,
|
|
4439
4686
|
updatedAt: now,
|
|
4440
|
-
warning
|
|
4687
|
+
warning,
|
|
4688
|
+
budgetTokens: input.budgetTokens ?? null,
|
|
4689
|
+
budgetFallbackModel: input.budgetFallbackModel ?? null,
|
|
4690
|
+
tokensUsed: 0,
|
|
4691
|
+
tokensWarnedAt: null
|
|
4441
4692
|
};
|
|
4442
4693
|
}
|
|
4443
4694
|
async function listTasks(input) {
|
|
@@ -4477,7 +4728,12 @@ async function listTasks(input) {
|
|
|
4477
4728
|
status: String(r.status),
|
|
4478
4729
|
taskFile: String(r.task_file),
|
|
4479
4730
|
createdAt: String(r.created_at),
|
|
4480
|
-
updatedAt: String(r.updated_at)
|
|
4731
|
+
updatedAt: String(r.updated_at),
|
|
4732
|
+
checkpointCount: Number(r.checkpoint_count ?? 0),
|
|
4733
|
+
budgetTokens: r.budget_tokens !== null ? Number(r.budget_tokens) : null,
|
|
4734
|
+
budgetFallbackModel: r.budget_fallback_model !== null ? String(r.budget_fallback_model) : null,
|
|
4735
|
+
tokensUsed: Number(r.tokens_used ?? 0),
|
|
4736
|
+
tokensWarnedAt: r.tokens_warned_at !== null ? Number(r.tokens_warned_at) : null
|
|
4481
4737
|
}));
|
|
4482
4738
|
}
|
|
4483
4739
|
function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
@@ -4485,8 +4741,13 @@ function checkStaleCompletion(taskContext, taskCreatedAt) {
|
|
|
4485
4741
|
if (!DELEGATION_KEYWORDS.test(taskContext)) return null;
|
|
4486
4742
|
try {
|
|
4487
4743
|
const since = new Date(taskCreatedAt).toISOString();
|
|
4744
|
+
const branch = execSync7(
|
|
4745
|
+
"git rev-parse --abbrev-ref HEAD 2>/dev/null",
|
|
4746
|
+
{ encoding: "utf8", timeout: 3e3 }
|
|
4747
|
+
).trim();
|
|
4748
|
+
const branchArg = branch && branch !== "HEAD" ? branch : "";
|
|
4488
4749
|
const commitCount = execSync7(
|
|
4489
|
-
`git log --oneline --since="${since}" 2>/dev/null | wc -l`,
|
|
4750
|
+
`git log --oneline --since="${since}" ${branchArg} 2>/dev/null | wc -l`,
|
|
4490
4751
|
{ encoding: "utf8", timeout: 5e3 }
|
|
4491
4752
|
).trim();
|
|
4492
4753
|
const count = parseInt(commitCount, 10);
|
|
@@ -4545,6 +4806,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4545
4806
|
const claimedBy = cur?.assigned_tmux ? ` (claimed by ${cur.assigned_tmux})` : "";
|
|
4546
4807
|
throw new Error(`${TASK_ALREADY_CLAIMED_PREFIX}: task ${taskId} is ${status}${claimedBy}`);
|
|
4547
4808
|
}
|
|
4809
|
+
try {
|
|
4810
|
+
await writeCheckpoint({
|
|
4811
|
+
taskId,
|
|
4812
|
+
step: "claimed",
|
|
4813
|
+
contextSummary: `Task claimed by session. Transitioning open \u2192 in_progress.`
|
|
4814
|
+
});
|
|
4815
|
+
} catch {
|
|
4816
|
+
}
|
|
4548
4817
|
return { row, taskFile, now, taskId };
|
|
4549
4818
|
}
|
|
4550
4819
|
if (input.result) {
|
|
@@ -4558,6 +4827,14 @@ ${input.result}` : `\u26A0\uFE0F ${warning}`;
|
|
|
4558
4827
|
args: [input.status, now, taskId]
|
|
4559
4828
|
});
|
|
4560
4829
|
}
|
|
4830
|
+
try {
|
|
4831
|
+
await writeCheckpoint({
|
|
4832
|
+
taskId,
|
|
4833
|
+
step: `status_transition:${input.status}`,
|
|
4834
|
+
contextSummary: input.result ? `Transitioned to ${input.status}. Result: ${input.result.slice(0, 500)}` : `Transitioned to ${input.status}.`
|
|
4835
|
+
});
|
|
4836
|
+
} catch {
|
|
4837
|
+
}
|
|
4561
4838
|
return { row, taskFile, now, taskId };
|
|
4562
4839
|
}
|
|
4563
4840
|
async function deleteTaskCore(taskId, _baseDir) {
|
|
@@ -4711,23 +4988,38 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
|
|
|
4711
4988
|
if (String(row.assigned_by) !== "system" || !taskFile.includes("review-")) return;
|
|
4712
4989
|
try {
|
|
4713
4990
|
const client = getClient();
|
|
4714
|
-
const
|
|
4715
|
-
const
|
|
4716
|
-
|
|
4717
|
-
if (parts.length >= 3 && parts[0] === "review") {
|
|
4718
|
-
const agent = parts[1];
|
|
4719
|
-
const slug = parts.slice(2).join("-");
|
|
4720
|
-
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
4991
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4992
|
+
const parentId = row.parent_task_id ? String(row.parent_task_id) : null;
|
|
4993
|
+
if (parentId) {
|
|
4721
4994
|
const result = await client.execute({
|
|
4722
|
-
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE
|
|
4723
|
-
args: [
|
|
4995
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE id = ? AND status = 'needs_review'",
|
|
4996
|
+
args: [now, parentId]
|
|
4724
4997
|
});
|
|
4725
4998
|
if (result.rowsAffected > 0) {
|
|
4726
4999
|
process.stderr.write(
|
|
4727
|
-
`[review-cleanup] Cascaded original task to done: ${
|
|
5000
|
+
`[review-cleanup] Cascaded original task to done via parent_task_id: ${parentId}
|
|
4728
5001
|
`
|
|
4729
5002
|
);
|
|
4730
5003
|
}
|
|
5004
|
+
} else {
|
|
5005
|
+
const fileName = taskFile.split("/").pop() ?? "";
|
|
5006
|
+
const reviewPrefix = fileName.replace(".md", "");
|
|
5007
|
+
const parts = reviewPrefix.split("-");
|
|
5008
|
+
if (parts.length >= 3 && parts[0] === "review") {
|
|
5009
|
+
const agent = parts[1];
|
|
5010
|
+
const slug = parts.slice(2).join("-");
|
|
5011
|
+
const originalTaskFile = `exe/${agent}/${slug}.md`;
|
|
5012
|
+
const result = await client.execute({
|
|
5013
|
+
sql: "UPDATE tasks SET status = 'done', updated_at = ? WHERE task_file = ? AND status = 'needs_review'",
|
|
5014
|
+
args: [now, originalTaskFile]
|
|
5015
|
+
});
|
|
5016
|
+
if (result.rowsAffected > 0) {
|
|
5017
|
+
process.stderr.write(
|
|
5018
|
+
`[review-cleanup] Cascaded original task to done (legacy path): ${originalTaskFile}
|
|
5019
|
+
`
|
|
5020
|
+
);
|
|
5021
|
+
}
|
|
5022
|
+
}
|
|
4731
5023
|
}
|
|
4732
5024
|
} catch (err) {
|
|
4733
5025
|
process.stderr.write(
|
|
@@ -4848,12 +5140,23 @@ function getProjectName(cwd2) {
|
|
|
4848
5140
|
const dir = cwd2 ?? process.cwd();
|
|
4849
5141
|
if (_cached2 && _cachedCwd === dir) return _cached2;
|
|
4850
5142
|
try {
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
|
|
4855
|
-
|
|
4856
|
-
|
|
5143
|
+
let repoRoot;
|
|
5144
|
+
try {
|
|
5145
|
+
const gitCommonDir = execSync8("git rev-parse --path-format=absolute --git-common-dir", {
|
|
5146
|
+
cwd: dir,
|
|
5147
|
+
encoding: "utf8",
|
|
5148
|
+
timeout: 2e3,
|
|
5149
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5150
|
+
}).trim();
|
|
5151
|
+
repoRoot = path18.dirname(gitCommonDir);
|
|
5152
|
+
} catch {
|
|
5153
|
+
repoRoot = execSync8("git rev-parse --show-toplevel", {
|
|
5154
|
+
cwd: dir,
|
|
5155
|
+
encoding: "utf8",
|
|
5156
|
+
timeout: 2e3,
|
|
5157
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
5158
|
+
}).trim();
|
|
5159
|
+
}
|
|
4857
5160
|
_cached2 = path18.basename(repoRoot);
|
|
4858
5161
|
_cachedCwd = dir;
|
|
4859
5162
|
return _cached2;
|
|
@@ -4959,7 +5262,9 @@ async function dispatchTaskToEmployee(input) {
|
|
|
4959
5262
|
return { dispatched, session: sessionName, crossProject };
|
|
4960
5263
|
} else {
|
|
4961
5264
|
const projectDir = input.projectDir ?? process.cwd();
|
|
4962
|
-
const result = ensureEmployee(input.assignedTo, exeSession, projectDir
|
|
5265
|
+
const result = ensureEmployee(input.assignedTo, exeSession, projectDir, {
|
|
5266
|
+
autoInstance: input.assignedTo === "tom" || input.assignedTo === "sasha"
|
|
5267
|
+
});
|
|
4963
5268
|
if (result.status === "failed") {
|
|
4964
5269
|
process.stderr.write(
|
|
4965
5270
|
`[dispatch] Failed to spawn ${input.assignedTo}: ${result.error}
|
|
@@ -5323,7 +5628,8 @@ __export(tasks_exports, {
|
|
|
5323
5628
|
resolveTask: () => resolveTask,
|
|
5324
5629
|
slugify: () => slugify,
|
|
5325
5630
|
updateTask: () => updateTask,
|
|
5326
|
-
updateTaskStatus: () => updateTaskStatus
|
|
5631
|
+
updateTaskStatus: () => updateTaskStatus,
|
|
5632
|
+
writeCheckpoint: () => writeCheckpoint
|
|
5327
5633
|
});
|
|
5328
5634
|
import path19 from "path";
|
|
5329
5635
|
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, unlinkSync as unlinkSync3 } from "fs";
|
|
@@ -5365,10 +5671,11 @@ async function updateTask(input) {
|
|
|
5365
5671
|
try {
|
|
5366
5672
|
const client = getClient();
|
|
5367
5673
|
const taskTitle = String(row.title);
|
|
5674
|
+
const escaped = taskTitle.replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
5368
5675
|
await client.execute({
|
|
5369
5676
|
sql: `UPDATE tasks SET status = 'cancelled', updated_at = ?
|
|
5370
|
-
WHERE title LIKE ? AND status IN ('open', 'in_progress')`,
|
|
5371
|
-
args: [now, `%left
|
|
5677
|
+
WHERE title LIKE ? ESCAPE '\\' AND status IN ('open', 'in_progress')`,
|
|
5678
|
+
args: [now, `%left '${escaped}' as in\\_progress%`]
|
|
5372
5679
|
});
|
|
5373
5680
|
} catch {
|
|
5374
5681
|
}
|
|
@@ -5426,6 +5733,10 @@ async function updateTask(input) {
|
|
|
5426
5733
|
taskFile,
|
|
5427
5734
|
createdAt: String(row.created_at),
|
|
5428
5735
|
updatedAt: now,
|
|
5736
|
+
budgetTokens: row.budget_tokens !== void 0 && row.budget_tokens !== null ? Number(row.budget_tokens) : null,
|
|
5737
|
+
budgetFallbackModel: row.budget_fallback_model !== void 0 && row.budget_fallback_model !== null ? String(row.budget_fallback_model) : null,
|
|
5738
|
+
tokensUsed: Number(row.tokens_used ?? 0),
|
|
5739
|
+
tokensWarnedAt: row.tokens_warned_at !== void 0 && row.tokens_warned_at !== null ? Number(row.tokens_warned_at) : null,
|
|
5429
5740
|
nextTask
|
|
5430
5741
|
};
|
|
5431
5742
|
}
|
|
@@ -5939,6 +6250,11 @@ function getSessionState(sessionName) {
|
|
|
5939
6250
|
if (!transport.isAlive(sessionName)) return "offline";
|
|
5940
6251
|
try {
|
|
5941
6252
|
const pane = transport.capturePane(sessionName, 5);
|
|
6253
|
+
if (!pane.includes("\u276F") && !pane.includes("Claude Code") && !BUSY_PATTERN.test(pane) && !/Running…/.test(pane)) {
|
|
6254
|
+
if (/\$\s*$/.test(pane) || /% $/.test(pane.trimEnd())) {
|
|
6255
|
+
return "no_claude";
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
5942
6258
|
if (/Running…/.test(pane)) return "tool";
|
|
5943
6259
|
if (BUSY_PATTERN.test(pane)) return "thinking";
|
|
5944
6260
|
return "idle";
|
|
@@ -5969,7 +6285,14 @@ function sendIntercom(targetSession) {
|
|
|
5969
6285
|
logIntercom(`SKIP \u2192 ${targetSession} (session not found)`);
|
|
5970
6286
|
return "failed";
|
|
5971
6287
|
}
|
|
5972
|
-
|
|
6288
|
+
const sessionState = getSessionState(targetSession);
|
|
6289
|
+
if (sessionState === "no_claude") {
|
|
6290
|
+
queueIntercom(targetSession, "claude not running in session");
|
|
6291
|
+
recordDebounce(targetSession);
|
|
6292
|
+
logIntercom(`QUEUED \u2192 ${targetSession} (no claude process \u2014 raw shell detected)`);
|
|
6293
|
+
return "queued";
|
|
6294
|
+
}
|
|
6295
|
+
if (sessionState === "thinking" || sessionState === "tool") {
|
|
5973
6296
|
queueIntercom(targetSession, "session busy at send time");
|
|
5974
6297
|
recordDebounce(targetSession);
|
|
5975
6298
|
logIntercom(`QUEUED \u2192 ${targetSession} (session busy, will retry from queue)`);
|
|
@@ -5981,18 +6304,7 @@ function sendIntercom(targetSession) {
|
|
|
5981
6304
|
}
|
|
5982
6305
|
transport.sendKeys(targetSession, "/exe-intercom");
|
|
5983
6306
|
recordDebounce(targetSession);
|
|
5984
|
-
|
|
5985
|
-
try {
|
|
5986
|
-
execSync9(`sleep ${INTERCOM_POLL_INTERVAL_S}`);
|
|
5987
|
-
} catch {
|
|
5988
|
-
}
|
|
5989
|
-
const state = getSessionState(targetSession);
|
|
5990
|
-
if (state === "thinking" || state === "tool") {
|
|
5991
|
-
logIntercom(`ACKNOWLEDGED \u2192 ${targetSession} (state=${state}, poll=${i + 1})`);
|
|
5992
|
-
return "acknowledged";
|
|
5993
|
-
}
|
|
5994
|
-
}
|
|
5995
|
-
logIntercom(`DELIVERED \u2192 ${targetSession} (no state transition after ${INTERCOM_POLL_MAX_ATTEMPTS}s)`);
|
|
6307
|
+
logIntercom(`DELIVERED \u2192 ${targetSession} (fire-and-forget)`);
|
|
5996
6308
|
return "delivered";
|
|
5997
6309
|
} catch {
|
|
5998
6310
|
logIntercom(`FAIL \u2192 ${targetSession}`);
|
|
@@ -6009,7 +6321,17 @@ function notifyParentExe(sessionKey) {
|
|
|
6009
6321
|
process.stderr.write(`[intercom] notifyParentExe \u2192 ${target}
|
|
6010
6322
|
`);
|
|
6011
6323
|
const result = sendIntercom(target);
|
|
6012
|
-
|
|
6324
|
+
if (result === "failed") {
|
|
6325
|
+
const rootExe = resolveExeSession();
|
|
6326
|
+
if (rootExe && rootExe !== target) {
|
|
6327
|
+
process.stderr.write(`[intercom] notifyParentExe: dispatcher ${target} dead, falling back to root exe ${rootExe}
|
|
6328
|
+
`);
|
|
6329
|
+
const fallback = sendIntercom(rootExe);
|
|
6330
|
+
return fallback !== "failed";
|
|
6331
|
+
}
|
|
6332
|
+
return false;
|
|
6333
|
+
}
|
|
6334
|
+
return true;
|
|
6013
6335
|
}
|
|
6014
6336
|
function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
6015
6337
|
if (employeeName === "exe") {
|
|
@@ -6058,7 +6380,8 @@ function ensureEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6058
6380
|
return { status: "failed", sessionName, error: "intercom delivery failed" };
|
|
6059
6381
|
}
|
|
6060
6382
|
const spawnOpts = { ...opts, instance: effectiveInstance };
|
|
6061
|
-
const
|
|
6383
|
+
const mainRoot = getMainRepoRoot(projectDir) ?? projectDir;
|
|
6384
|
+
const wtPath = ensureWorktree(mainRoot, employeeName, effectiveInstance);
|
|
6062
6385
|
if (wtPath) {
|
|
6063
6386
|
spawnOpts.cwd = wtPath;
|
|
6064
6387
|
}
|
|
@@ -6239,7 +6562,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6239
6562
|
let booted = false;
|
|
6240
6563
|
for (let i = 0; i < 30; i++) {
|
|
6241
6564
|
try {
|
|
6242
|
-
execSync9("sleep
|
|
6565
|
+
execSync9("sleep 0.5");
|
|
6243
6566
|
} catch {
|
|
6244
6567
|
}
|
|
6245
6568
|
try {
|
|
@@ -6259,7 +6582,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6259
6582
|
}
|
|
6260
6583
|
}
|
|
6261
6584
|
if (!booted) {
|
|
6262
|
-
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within
|
|
6585
|
+
return { sessionName, error: `${useExeAgent ? "exe-agent" : "claude"} did not boot within 15s` };
|
|
6263
6586
|
}
|
|
6264
6587
|
if (!useExeAgent) {
|
|
6265
6588
|
try {
|
|
@@ -6277,7 +6600,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
|
|
|
6277
6600
|
});
|
|
6278
6601
|
return { sessionName };
|
|
6279
6602
|
}
|
|
6280
|
-
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN
|
|
6603
|
+
var SESSION_CACHE, BEHAVIORS_EXPORT_TIMEOUT_MS, VERIFY_PANE_LINES, INTERCOM_DEBOUNCE_MS, INTERCOM_LOG2, DEBOUNCE_FILE, DEBOUNCE_CLEANUP_AGE_MS, BUSY_PATTERN;
|
|
6281
6604
|
var init_tmux_routing = __esm({
|
|
6282
6605
|
"src/lib/tmux-routing.ts"() {
|
|
6283
6606
|
"use strict";
|
|
@@ -6298,8 +6621,6 @@ var init_tmux_routing = __esm({
|
|
|
6298
6621
|
DEBOUNCE_FILE = path20.join(SESSION_CACHE, "intercom-debounce.json");
|
|
6299
6622
|
DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
|
|
6300
6623
|
BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
|
|
6301
|
-
INTERCOM_POLL_INTERVAL_S = 1;
|
|
6302
|
-
INTERCOM_POLL_MAX_ATTEMPTS = 8;
|
|
6303
6624
|
}
|
|
6304
6625
|
});
|
|
6305
6626
|
|
|
@@ -7241,13 +7562,27 @@ async function ensureShardSchema(client) {
|
|
|
7241
7562
|
"ALTER TABLE memories ADD COLUMN document_id TEXT",
|
|
7242
7563
|
"ALTER TABLE memories ADD COLUMN user_id TEXT",
|
|
7243
7564
|
"ALTER TABLE memories ADD COLUMN char_offset INTEGER",
|
|
7244
|
-
"ALTER TABLE memories ADD COLUMN page_number INTEGER"
|
|
7565
|
+
"ALTER TABLE memories ADD COLUMN page_number INTEGER",
|
|
7566
|
+
// Source provenance columns (must match database.ts)
|
|
7567
|
+
"ALTER TABLE memories ADD COLUMN source_path TEXT",
|
|
7568
|
+
"ALTER TABLE memories ADD COLUMN source_type TEXT DEFAULT 'text'",
|
|
7569
|
+
"ALTER TABLE memories ADD COLUMN tier INTEGER DEFAULT 3",
|
|
7570
|
+
"ALTER TABLE memories ADD COLUMN supersedes_id TEXT"
|
|
7245
7571
|
]) {
|
|
7246
7572
|
try {
|
|
7247
7573
|
await client.execute(col);
|
|
7248
7574
|
} catch {
|
|
7249
7575
|
}
|
|
7250
7576
|
}
|
|
7577
|
+
for (const idx of [
|
|
7578
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_tier ON memories(tier)",
|
|
7579
|
+
"CREATE INDEX IF NOT EXISTS idx_memories_supersedes ON memories(supersedes_id) WHERE supersedes_id IS NOT NULL"
|
|
7580
|
+
]) {
|
|
7581
|
+
try {
|
|
7582
|
+
await client.execute(idx);
|
|
7583
|
+
} catch {
|
|
7584
|
+
}
|
|
7585
|
+
}
|
|
7251
7586
|
try {
|
|
7252
7587
|
await client.execute("CREATE INDEX IF NOT EXISTS idx_memories_status ON memories(status)");
|
|
7253
7588
|
} catch {
|
|
@@ -7356,8 +7691,11 @@ var store_exports = {};
|
|
|
7356
7691
|
__export(store_exports, {
|
|
7357
7692
|
attachDocumentMetadata: () => attachDocumentMetadata,
|
|
7358
7693
|
buildWikiScopeFilter: () => buildWikiScopeFilter,
|
|
7694
|
+
classifyTier: () => classifyTier,
|
|
7359
7695
|
disposeStore: () => disposeStore,
|
|
7360
7696
|
flushBatch: () => flushBatch,
|
|
7697
|
+
flushTier3: () => flushTier3,
|
|
7698
|
+
getMemoryCardinality: () => getMemoryCardinality,
|
|
7361
7699
|
initStore: () => initStore,
|
|
7362
7700
|
reserveVersions: () => reserveVersions,
|
|
7363
7701
|
searchMemories: () => searchMemories,
|
|
@@ -7403,6 +7741,11 @@ async function initStore(options) {
|
|
|
7403
7741
|
const vResult = await client.execute("SELECT MAX(version) as max_v FROM memories");
|
|
7404
7742
|
_nextVersion = (Number(vResult.rows[0]?.max_v) || 0) + 1;
|
|
7405
7743
|
}
|
|
7744
|
+
function classifyTier(record) {
|
|
7745
|
+
if (record.tool_name === "commit_to_long_term_memory" && (record.importance ?? 0) >= 8) return 1;
|
|
7746
|
+
if (["store_memory", "manual"].includes(record.tool_name ?? "") && (record.importance ?? 0) >= 5) return 2;
|
|
7747
|
+
return 3;
|
|
7748
|
+
}
|
|
7406
7749
|
async function writeMemory(record) {
|
|
7407
7750
|
if (record.vector !== null && record.vector.length !== EMBEDDING_DIM) {
|
|
7408
7751
|
throw new Error(
|
|
@@ -7430,7 +7773,11 @@ async function writeMemory(record) {
|
|
|
7430
7773
|
document_id: record.document_id ?? null,
|
|
7431
7774
|
user_id: record.user_id ?? null,
|
|
7432
7775
|
char_offset: record.char_offset ?? null,
|
|
7433
|
-
page_number: record.page_number ?? null
|
|
7776
|
+
page_number: record.page_number ?? null,
|
|
7777
|
+
source_path: record.source_path ?? null,
|
|
7778
|
+
source_type: record.source_type ?? null,
|
|
7779
|
+
tier: record.tier ?? classifyTier(record),
|
|
7780
|
+
supersedes_id: record.supersedes_id ?? null
|
|
7434
7781
|
};
|
|
7435
7782
|
_pendingRecords.push(dbRow);
|
|
7436
7783
|
if (_flushTimer === null) {
|
|
@@ -7462,20 +7809,26 @@ async function flushBatch() {
|
|
|
7462
7809
|
const userId = row.user_id ?? null;
|
|
7463
7810
|
const charOffset = row.char_offset ?? null;
|
|
7464
7811
|
const pageNumber = row.page_number ?? null;
|
|
7812
|
+
const sourcePath = row.source_path ?? null;
|
|
7813
|
+
const sourceType = row.source_type ?? null;
|
|
7814
|
+
const tier = row.tier ?? 3;
|
|
7815
|
+
const supersedesId = row.supersedes_id ?? null;
|
|
7465
7816
|
return {
|
|
7466
7817
|
sql: hasVector ? `INSERT OR IGNORE INTO memories
|
|
7467
7818
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
7468
7819
|
tool_name, project_name,
|
|
7469
7820
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
7470
7821
|
confidence, last_accessed,
|
|
7471
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
7472
|
-
|
|
7822
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
7823
|
+
source_path, source_type, tier, supersedes_id)
|
|
7824
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, vector32(?), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` : `INSERT OR IGNORE INTO memories
|
|
7473
7825
|
(id, agent_id, agent_role, session_id, timestamp,
|
|
7474
7826
|
tool_name, project_name,
|
|
7475
7827
|
has_error, raw_text, vector, version, task_id, importance, status,
|
|
7476
7828
|
confidence, last_accessed,
|
|
7477
|
-
workspace_id, document_id, user_id, char_offset, page_number
|
|
7478
|
-
|
|
7829
|
+
workspace_id, document_id, user_id, char_offset, page_number,
|
|
7830
|
+
source_path, source_type, tier, supersedes_id)
|
|
7831
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
7479
7832
|
args: hasVector ? [
|
|
7480
7833
|
row.id,
|
|
7481
7834
|
row.agent_id,
|
|
@@ -7497,7 +7850,11 @@ async function flushBatch() {
|
|
|
7497
7850
|
documentId,
|
|
7498
7851
|
userId,
|
|
7499
7852
|
charOffset,
|
|
7500
|
-
pageNumber
|
|
7853
|
+
pageNumber,
|
|
7854
|
+
sourcePath,
|
|
7855
|
+
sourceType,
|
|
7856
|
+
tier,
|
|
7857
|
+
supersedesId
|
|
7501
7858
|
] : [
|
|
7502
7859
|
row.id,
|
|
7503
7860
|
row.agent_id,
|
|
@@ -7518,7 +7875,11 @@ async function flushBatch() {
|
|
|
7518
7875
|
documentId,
|
|
7519
7876
|
userId,
|
|
7520
7877
|
charOffset,
|
|
7521
|
-
pageNumber
|
|
7878
|
+
pageNumber,
|
|
7879
|
+
sourcePath,
|
|
7880
|
+
sourceType,
|
|
7881
|
+
tier,
|
|
7882
|
+
supersedesId
|
|
7522
7883
|
]
|
|
7523
7884
|
};
|
|
7524
7885
|
};
|
|
@@ -7592,7 +7953,8 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
7592
7953
|
has_error, raw_text, vector, importance, status,
|
|
7593
7954
|
confidence, last_accessed,
|
|
7594
7955
|
workspace_id, document_id, user_id,
|
|
7595
|
-
char_offset, page_number
|
|
7956
|
+
char_offset, page_number,
|
|
7957
|
+
source_path, source_type
|
|
7596
7958
|
FROM memories
|
|
7597
7959
|
WHERE agent_id = ?
|
|
7598
7960
|
AND vector IS NOT NULL${statusFilter}
|
|
@@ -7641,7 +8003,9 @@ async function searchMemories(queryVector, agentId, options) {
|
|
|
7641
8003
|
document_id: row.document_id ?? null,
|
|
7642
8004
|
user_id: row.user_id ?? null,
|
|
7643
8005
|
char_offset: row.char_offset ?? null,
|
|
7644
|
-
page_number: row.page_number ?? null
|
|
8006
|
+
page_number: row.page_number ?? null,
|
|
8007
|
+
source_path: row.source_path ?? null,
|
|
8008
|
+
source_type: row.source_type ?? null
|
|
7645
8009
|
}));
|
|
7646
8010
|
}
|
|
7647
8011
|
async function attachDocumentMetadata(records) {
|
|
@@ -7679,6 +8043,25 @@ async function attachDocumentMetadata(records) {
|
|
|
7679
8043
|
}
|
|
7680
8044
|
return records;
|
|
7681
8045
|
}
|
|
8046
|
+
async function flushTier3(agentId, options) {
|
|
8047
|
+
const client = getClient();
|
|
8048
|
+
const maxAge = options?.maxAgeHours ?? 72;
|
|
8049
|
+
const cutoff = new Date(Date.now() - maxAge * 36e5).toISOString();
|
|
8050
|
+
if (options?.dryRun) {
|
|
8051
|
+
const result2 = await client.execute({
|
|
8052
|
+
sql: `SELECT COUNT(*) as cnt FROM memories
|
|
8053
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
8054
|
+
args: [agentId, cutoff]
|
|
8055
|
+
});
|
|
8056
|
+
return { archived: Number(result2.rows[0]?.cnt ?? 0) };
|
|
8057
|
+
}
|
|
8058
|
+
const result = await client.execute({
|
|
8059
|
+
sql: `UPDATE memories SET status = 'archived'
|
|
8060
|
+
WHERE agent_id = ? AND tier = 3 AND status = 'active' AND timestamp < ?`,
|
|
8061
|
+
args: [agentId, cutoff]
|
|
8062
|
+
});
|
|
8063
|
+
return { archived: result.rowsAffected };
|
|
8064
|
+
}
|
|
7682
8065
|
async function disposeStore() {
|
|
7683
8066
|
if (_flushTimer !== null) {
|
|
7684
8067
|
clearInterval(_flushTimer);
|
|
@@ -7709,6 +8092,18 @@ function reserveVersions(count) {
|
|
|
7709
8092
|
}
|
|
7710
8093
|
return reserved;
|
|
7711
8094
|
}
|
|
8095
|
+
async function getMemoryCardinality(agentId) {
|
|
8096
|
+
try {
|
|
8097
|
+
const client = getClient();
|
|
8098
|
+
const result = await client.execute({
|
|
8099
|
+
sql: `SELECT COUNT(*) as cnt FROM memories WHERE agent_id = ? AND COALESCE(status, 'active') = 'active'`,
|
|
8100
|
+
args: [agentId]
|
|
8101
|
+
});
|
|
8102
|
+
return Number(result.rows[0]?.cnt) || 0;
|
|
8103
|
+
} catch {
|
|
8104
|
+
return 0;
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
7712
8107
|
var _pendingRecords, _batchSize, _flushIntervalMs, _flushTimer, _flushing, _nextVersion;
|
|
7713
8108
|
var init_store = __esm({
|
|
7714
8109
|
"src/lib/store.ts"() {
|