sisyphi 1.1.18 → 1.1.19
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 +195 -75
- package/dist/chunk-36VJ7ZBD.js +1898 -0
- package/dist/chunk-36VJ7ZBD.js.map +1 -0
- package/dist/{chunk-C2XKXERJ.js → chunk-M6Z3KHOH.js} +159 -46
- package/dist/chunk-M6Z3KHOH.js.map +1 -0
- package/dist/chunk-O4ZHSQ5R.js +544 -0
- package/dist/chunk-O4ZHSQ5R.js.map +1 -0
- package/dist/chunk-P2HHTIPM.js +478 -0
- package/dist/chunk-P2HHTIPM.js.map +1 -0
- package/dist/{chunk-TMBAVPHH.js → chunk-PNDCVKBN.js} +73 -1
- package/dist/chunk-PNDCVKBN.js.map +1 -0
- package/dist/chunk-SVGIQ2G4.js +1076 -0
- package/dist/chunk-SVGIQ2G4.js.map +1 -0
- package/dist/cli.js +4405 -892
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +4340 -1990
- package/dist/daemon.js.map +1 -1
- package/dist/{paths-XRDEEJ5R.js → paths-JXFLR5BN.js} +38 -2
- package/dist/single-ask-6G4BIVY2.js +132 -0
- package/dist/single-ask-6G4BIVY2.js.map +1 -0
- package/dist/templates/CLAUDE.md +1 -56
- package/dist/templates/agent-plugin/agents/CLAUDE.md +2 -65
- package/dist/templates/agent-plugin/agents/debug.md +43 -6
- package/dist/templates/agent-plugin/agents/debug.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/explore.md +28 -1
- package/dist/templates/agent-plugin/agents/explore.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/implementor.md +94 -0
- package/dist/templates/agent-plugin/agents/implementor.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/operator.md +43 -1
- package/dist/templates/agent-plugin/agents/operator.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/plan/sub-planner.md +75 -0
- package/dist/templates/agent-plugin/agents/plan.md +176 -86
- package/dist/templates/agent-plugin/agents/plan.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/problem/adversarial.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/contrarian.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/first-principles.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/precedent.md +25 -0
- package/dist/templates/agent-plugin/agents/problem/simplifier.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/systems-thinker.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/time-traveler.md +26 -0
- package/dist/templates/agent-plugin/agents/problem/user-empathy.md +26 -0
- package/dist/templates/agent-plugin/agents/problem.md +334 -79
- package/dist/templates/agent-plugin/agents/problem.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/research-lead/CLAUDE.md +26 -0
- package/dist/templates/agent-plugin/agents/research-lead/critic.md +61 -0
- package/dist/templates/agent-plugin/agents/research-lead/researcher.md +60 -0
- package/dist/templates/agent-plugin/agents/research-lead.md +184 -0
- package/dist/templates/agent-plugin/agents/research-lead.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/review/CLAUDE.md +3 -29
- package/dist/templates/agent-plugin/agents/review/compliance.md +14 -3
- package/dist/templates/agent-plugin/agents/review/efficiency.md +15 -4
- package/dist/templates/agent-plugin/agents/review/quality.md +20 -6
- package/dist/templates/agent-plugin/agents/review/reuse.md +17 -5
- package/dist/templates/agent-plugin/agents/review/security.md +10 -3
- package/dist/templates/agent-plugin/agents/review/tests.md +58 -0
- package/dist/templates/agent-plugin/agents/review-plan/CLAUDE.md +28 -0
- package/dist/templates/agent-plugin/agents/review-plan/code-smells.md +4 -2
- package/dist/templates/agent-plugin/agents/review-plan/pattern-consistency.md +4 -2
- package/dist/templates/agent-plugin/agents/review-plan/requirements-coverage.md +3 -1
- package/dist/templates/agent-plugin/agents/review-plan/security.md +5 -2
- package/dist/templates/agent-plugin/agents/review-plan.md +52 -5
- package/dist/templates/agent-plugin/agents/review-plan.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/review.md +89 -16
- package/dist/templates/agent-plugin/agents/review.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/spec/engineer.md +175 -0
- package/dist/templates/agent-plugin/agents/spec/requirements-writer.md +149 -0
- package/dist/templates/agent-plugin/agents/spec.md +444 -0
- package/dist/templates/agent-plugin/agents/spec.settings.json +57 -0
- package/dist/templates/agent-plugin/agents/test-spec.md +58 -2
- package/dist/templates/agent-plugin/agents/test-spec.settings.json +57 -0
- package/dist/templates/agent-plugin/hooks/CLAUDE.md +9 -57
- package/dist/templates/agent-plugin/hooks/ask-background-guard.sh +57 -0
- package/dist/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/dist/templates/agent-plugin/hooks/plan-user-prompt.sh +8 -7
- package/dist/templates/agent-plugin/hooks/plan-validate.sh +97 -0
- package/dist/templates/agent-plugin/hooks/plan-write-path.sh +55 -0
- package/dist/templates/agent-plugin/hooks/problem-user-prompt.sh +26 -0
- package/dist/templates/agent-plugin/hooks/register-bg-task.sh +37 -0
- package/dist/templates/agent-plugin/hooks/require-submit.sh +51 -42
- package/dist/templates/agent-plugin/hooks/review-user-prompt.sh +6 -2
- package/dist/templates/agent-plugin/hooks/spec-user-prompt.sh +43 -0
- package/dist/templates/agent-plugin/skills/humanloop/SKILL.md +147 -0
- package/dist/templates/agent-plugin/skills/perspective-fanout/SKILL.md +115 -0
- package/dist/templates/agent-plugin/skills/problem-document/SKILL.md +105 -0
- package/dist/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +83 -0
- package/dist/templates/agent-suffix.md +7 -4
- package/dist/templates/baleia.lua +42 -0
- package/dist/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
- package/dist/templates/dashboard-claude.md +7 -3
- package/dist/templates/orchestrator-base.md +89 -52
- package/dist/templates/orchestrator-completion.md +47 -24
- package/dist/templates/orchestrator-discovery.md +183 -0
- package/dist/templates/orchestrator-impl.md +47 -18
- package/dist/templates/orchestrator-planning.md +109 -20
- package/dist/templates/orchestrator-plugin/commands/sisyphus/scratch.md +19 -0
- package/dist/templates/orchestrator-plugin/commands/sisyphus/spec.md +11 -0
- package/dist/templates/orchestrator-plugin/commands/sisyphus/strategize.md +5 -5
- package/dist/templates/orchestrator-plugin/hooks/hooks.json +0 -10
- package/dist/templates/orchestrator-plugin/skills/humanloop/SKILL.md +149 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/SKILL.md +2 -1
- package/dist/templates/orchestrator-plugin/skills/orchestration/strategy.md +160 -0
- package/dist/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +26 -28
- package/dist/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +133 -25
- package/dist/templates/orchestrator-settings.json +55 -0
- package/dist/templates/orchestrator-validation.md +17 -14
- package/dist/templates/sisyphus-init.lua +30 -0
- package/dist/templates/sisyphus-tmux-plugin/hooks/hooks.json +54 -0
- package/dist/templates/sisyphus-tmux-plugin/hooks/tmux-state.sh +19 -0
- package/dist/templates/termrender-haiku-system.md +82 -0
- package/dist/templates/whip-animation.sh +345 -0
- package/dist/tui.js +3242 -2189
- package/dist/tui.js.map +1 -1
- package/native/SisyphusNotify/main.swift +15 -5
- package/package.json +8 -6
- package/templates/CLAUDE.md +1 -56
- package/templates/agent-plugin/agents/CLAUDE.md +2 -65
- package/templates/agent-plugin/agents/debug.md +43 -6
- package/templates/agent-plugin/agents/debug.settings.json +57 -0
- package/templates/agent-plugin/agents/explore.md +28 -1
- package/templates/agent-plugin/agents/explore.settings.json +57 -0
- package/templates/agent-plugin/agents/implementor.md +94 -0
- package/templates/agent-plugin/agents/implementor.settings.json +57 -0
- package/templates/agent-plugin/agents/operator.md +43 -1
- package/templates/agent-plugin/agents/operator.settings.json +57 -0
- package/templates/agent-plugin/agents/plan/sub-planner.md +75 -0
- package/templates/agent-plugin/agents/plan.md +176 -86
- package/templates/agent-plugin/agents/plan.settings.json +57 -0
- package/templates/agent-plugin/agents/problem/adversarial.md +26 -0
- package/templates/agent-plugin/agents/problem/contrarian.md +26 -0
- package/templates/agent-plugin/agents/problem/first-principles.md +26 -0
- package/templates/agent-plugin/agents/problem/precedent.md +25 -0
- package/templates/agent-plugin/agents/problem/simplifier.md +26 -0
- package/templates/agent-plugin/agents/problem/systems-thinker.md +26 -0
- package/templates/agent-plugin/agents/problem/time-traveler.md +26 -0
- package/templates/agent-plugin/agents/problem/user-empathy.md +26 -0
- package/templates/agent-plugin/agents/problem.md +334 -79
- package/templates/agent-plugin/agents/problem.settings.json +57 -0
- package/templates/agent-plugin/agents/research-lead/CLAUDE.md +26 -0
- package/templates/agent-plugin/agents/research-lead/critic.md +61 -0
- package/templates/agent-plugin/agents/research-lead/researcher.md +60 -0
- package/templates/agent-plugin/agents/research-lead.md +184 -0
- package/templates/agent-plugin/agents/research-lead.settings.json +57 -0
- package/templates/agent-plugin/agents/review/CLAUDE.md +3 -29
- package/templates/agent-plugin/agents/review/compliance.md +14 -3
- package/templates/agent-plugin/agents/review/efficiency.md +15 -4
- package/templates/agent-plugin/agents/review/quality.md +20 -6
- package/templates/agent-plugin/agents/review/reuse.md +17 -5
- package/templates/agent-plugin/agents/review/security.md +10 -3
- package/templates/agent-plugin/agents/review/tests.md +58 -0
- package/templates/agent-plugin/agents/review-plan/CLAUDE.md +28 -0
- package/templates/agent-plugin/agents/review-plan/code-smells.md +4 -2
- package/templates/agent-plugin/agents/review-plan/pattern-consistency.md +4 -2
- package/templates/agent-plugin/agents/review-plan/requirements-coverage.md +3 -1
- package/templates/agent-plugin/agents/review-plan/security.md +5 -2
- package/templates/agent-plugin/agents/review-plan.md +52 -5
- package/templates/agent-plugin/agents/review-plan.settings.json +57 -0
- package/templates/agent-plugin/agents/review.md +89 -16
- package/templates/agent-plugin/agents/review.settings.json +57 -0
- package/templates/agent-plugin/agents/spec/engineer.md +175 -0
- package/templates/agent-plugin/agents/spec/requirements-writer.md +149 -0
- package/templates/agent-plugin/agents/spec.md +444 -0
- package/templates/agent-plugin/agents/spec.settings.json +57 -0
- package/templates/agent-plugin/agents/test-spec.md +58 -2
- package/templates/agent-plugin/agents/test-spec.settings.json +57 -0
- package/templates/agent-plugin/hooks/CLAUDE.md +9 -57
- package/templates/agent-plugin/hooks/ask-background-guard.sh +57 -0
- package/templates/agent-plugin/hooks/intercept-send-message.sh +1 -1
- package/templates/agent-plugin/hooks/plan-user-prompt.sh +8 -7
- package/templates/agent-plugin/hooks/plan-validate.sh +97 -0
- package/templates/agent-plugin/hooks/plan-write-path.sh +55 -0
- package/templates/agent-plugin/hooks/problem-user-prompt.sh +26 -0
- package/templates/agent-plugin/hooks/register-bg-task.sh +37 -0
- package/templates/agent-plugin/hooks/require-submit.sh +51 -42
- package/templates/agent-plugin/hooks/review-user-prompt.sh +6 -2
- package/templates/agent-plugin/hooks/spec-user-prompt.sh +43 -0
- package/templates/agent-plugin/skills/humanloop/SKILL.md +147 -0
- package/templates/agent-plugin/skills/perspective-fanout/SKILL.md +115 -0
- package/templates/agent-plugin/skills/problem-document/SKILL.md +105 -0
- package/templates/agent-plugin/skills/problem-plateau-breakers/SKILL.md +83 -0
- package/templates/agent-suffix.md +7 -4
- package/templates/baleia.lua +42 -0
- package/templates/companion-plugin/hooks/user-prompt-context.sh +1 -1
- package/templates/dashboard-claude.md +7 -3
- package/templates/orchestrator-base.md +89 -52
- package/templates/orchestrator-completion.md +47 -24
- package/templates/orchestrator-discovery.md +183 -0
- package/templates/orchestrator-impl.md +47 -18
- package/templates/orchestrator-planning.md +109 -20
- package/templates/orchestrator-plugin/commands/sisyphus/scratch.md +19 -0
- package/templates/orchestrator-plugin/commands/sisyphus/spec.md +11 -0
- package/templates/orchestrator-plugin/commands/sisyphus/strategize.md +5 -5
- package/templates/orchestrator-plugin/hooks/hooks.json +0 -10
- package/templates/orchestrator-plugin/skills/humanloop/SKILL.md +149 -0
- package/templates/orchestrator-plugin/skills/orchestration/CLAUDE.md +1 -0
- package/templates/orchestrator-plugin/skills/orchestration/SKILL.md +2 -1
- package/templates/orchestrator-plugin/skills/orchestration/strategy.md +160 -0
- package/templates/orchestrator-plugin/skills/orchestration/task-patterns.md +26 -28
- package/templates/orchestrator-plugin/skills/orchestration/workflow-examples.md +133 -25
- package/templates/orchestrator-settings.json +55 -0
- package/templates/orchestrator-validation.md +17 -14
- package/templates/sisyphus-init.lua +30 -0
- package/templates/sisyphus-tmux-plugin/hooks/hooks.json +54 -0
- package/templates/sisyphus-tmux-plugin/hooks/tmux-state.sh +19 -0
- package/templates/termrender-haiku-system.md +82 -0
- package/templates/whip-animation.sh +345 -0
- package/dist/chunk-22ZGZTGY.js +0 -67
- package/dist/chunk-22ZGZTGY.js.map +0 -1
- package/dist/chunk-6PJVJEYQ.js +0 -46
- package/dist/chunk-6PJVJEYQ.js.map +0 -1
- package/dist/chunk-C2XKXERJ.js.map +0 -1
- package/dist/chunk-TMBAVPHH.js.map +0 -1
- package/dist/chunk-V36NXMHP.js +0 -299
- package/dist/chunk-V36NXMHP.js.map +0 -1
- package/dist/templates/agent-plugin/agents/design.md +0 -134
- package/dist/templates/agent-plugin/agents/requirements.md +0 -138
- package/dist/templates/begin.md +0 -22
- package/dist/templates/nvim-tutorial.txt +0 -68
- package/dist/templates/orchestrator-plugin/commands/sisyphus/design.md +0 -13
- package/dist/templates/orchestrator-plugin/commands/sisyphus/requirements.md +0 -13
- package/dist/templates/orchestrator-plugin/hooks/idle-notify.sh +0 -71
- package/dist/templates/orchestrator-strategy.md +0 -238
- package/templates/agent-plugin/agents/design.md +0 -134
- package/templates/agent-plugin/agents/requirements.md +0 -138
- package/templates/begin.md +0 -22
- package/templates/nvim-tutorial.txt +0 -68
- package/templates/orchestrator-plugin/commands/sisyphus/design.md +0 -13
- package/templates/orchestrator-plugin/commands/sisyphus/requirements.md +0 -13
- package/templates/orchestrator-plugin/hooks/idle-notify.sh +0 -71
- package/templates/orchestrator-strategy.md +0 -238
- /package/dist/{paths-XRDEEJ5R.js.map → paths-JXFLR5BN.js.map} +0 -0
|
@@ -0,0 +1,1898 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
companionMemoryPath,
|
|
4
|
+
companionPath,
|
|
5
|
+
historySessionDir,
|
|
6
|
+
sessionDir,
|
|
7
|
+
statePath
|
|
8
|
+
} from "./chunk-PNDCVKBN.js";
|
|
9
|
+
|
|
10
|
+
// src/shared/env.ts
|
|
11
|
+
import { resolve } from "path";
|
|
12
|
+
function augmentedPath() {
|
|
13
|
+
const rawPath = process.env["PATH"];
|
|
14
|
+
const basePath = rawPath !== void 0 && rawPath.length > 0 ? rawPath : "/usr/bin:/bin";
|
|
15
|
+
const home = process.env["HOME"];
|
|
16
|
+
const candidates = [
|
|
17
|
+
...home ? [`${home}/.local/bin`] : [],
|
|
18
|
+
// Claude CLI, pipx, user-local installs
|
|
19
|
+
resolve(process.execPath, ".."),
|
|
20
|
+
// Node.js bin dir (ensures node/npm available)
|
|
21
|
+
"/opt/homebrew/bin",
|
|
22
|
+
// Homebrew (Apple Silicon macOS)
|
|
23
|
+
"/opt/homebrew/sbin",
|
|
24
|
+
// Homebrew sbin
|
|
25
|
+
"/usr/local/bin",
|
|
26
|
+
// Homebrew (Intel macOS), manual installs
|
|
27
|
+
"/usr/local/sbin",
|
|
28
|
+
// Manual installs
|
|
29
|
+
"/opt/local/bin",
|
|
30
|
+
// MacPorts
|
|
31
|
+
"/opt/local/sbin",
|
|
32
|
+
// MacPorts
|
|
33
|
+
"/home/linuxbrew/.linuxbrew/bin"
|
|
34
|
+
// Linuxbrew
|
|
35
|
+
];
|
|
36
|
+
const nixProfile = process.env["NIX_PROFILES"];
|
|
37
|
+
if (nixProfile) {
|
|
38
|
+
for (const p of nixProfile.split(" ").reverse()) {
|
|
39
|
+
candidates.push(`${p}/bin`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const existing = new Set(basePath.split(":"));
|
|
43
|
+
const prepend = candidates.filter((dir) => !existing.has(dir));
|
|
44
|
+
return prepend.length > 0 ? `${prepend.join(":")}:${basePath}` : basePath;
|
|
45
|
+
}
|
|
46
|
+
function execEnv() {
|
|
47
|
+
return {
|
|
48
|
+
...process.env,
|
|
49
|
+
PATH: augmentedPath()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/shared/exec.ts
|
|
54
|
+
import { execSync } from "child_process";
|
|
55
|
+
var EXEC_ENV = execEnv();
|
|
56
|
+
function exec(cmd, cwd, timeoutMs = 3e4) {
|
|
57
|
+
return execSync(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, timeout: timeoutMs }).trim();
|
|
58
|
+
}
|
|
59
|
+
function execSafe(cmd, cwd, timeoutMs) {
|
|
60
|
+
try {
|
|
61
|
+
return execSync(cmd, { encoding: "utf-8", env: EXEC_ENV, cwd, stdio: ["pipe", "pipe", "pipe"], timeout: timeoutMs }).trim();
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/shared/companion-types.ts
|
|
68
|
+
var OBSERVATION_CATEGORIES = ["session-sentiments", "repo-impressions", "user-patterns", "notable-moments"];
|
|
69
|
+
var MemoryStoreParseError = class extends Error {
|
|
70
|
+
constructor(cause) {
|
|
71
|
+
super("companion-memory.json is corrupt");
|
|
72
|
+
this.cause = cause;
|
|
73
|
+
}
|
|
74
|
+
cause;
|
|
75
|
+
};
|
|
76
|
+
var ACHIEVEMENTS = [
|
|
77
|
+
// Milestone (25)
|
|
78
|
+
{ id: "first-blood", name: "First Blood", category: "milestone", description: "Complete your first session.", badge: null },
|
|
79
|
+
{ id: "regular", name: "Regular", category: "milestone", description: "Complete 10 sessions.", badge: null },
|
|
80
|
+
{ id: "centurion", name: "Centurion", category: "milestone", description: "Complete 100 sessions.", badge: null },
|
|
81
|
+
{ id: "veteran", name: "Veteran", category: "milestone", description: "Complete 500 sessions.", badge: null },
|
|
82
|
+
{ id: "thousand-boulder", name: "Thousand Boulder", category: "milestone", description: "Complete 1,000 sessions.", badge: null },
|
|
83
|
+
{ id: "cartographer", name: "Cartographer", category: "milestone", description: "Work in 5 different repos.", badge: "+" },
|
|
84
|
+
{ id: "world-traveler", name: "World Traveler", category: "milestone", description: "Work in 15 different repos.", badge: null },
|
|
85
|
+
{ id: "omnipresent", name: "Omnipresent", category: "milestone", description: "Work in 30 different repos.", badge: null },
|
|
86
|
+
{ id: "swarm-starter", name: "Swarm Starter", category: "milestone", description: "Spawn 50 agents over a lifetime.", badge: null },
|
|
87
|
+
{ id: "hive-mind", name: "Hive Mind", category: "milestone", description: "Spawn 500 agents over a lifetime.", badge: null },
|
|
88
|
+
{ id: "legion", name: "Legion", category: "milestone", description: "Spawn 2,000 agents over a lifetime.", badge: null },
|
|
89
|
+
{ id: "army-of-thousands", name: "Army of Thousands", category: "milestone", description: "Spawn 5,000 agents over a lifetime.", badge: null },
|
|
90
|
+
{ id: "singularity", name: "Singularity", category: "milestone", description: "Spawn 10,000 agents over a lifetime.", badge: null },
|
|
91
|
+
{ id: "first-shift", name: "First Shift", category: "milestone", description: "10 hours of total agent active time.", badge: null },
|
|
92
|
+
{ id: "workaholic", name: "Workaholic", category: "milestone", description: "100 hours of total agent active time.", badge: null },
|
|
93
|
+
{ id: "time-lord", name: "Time Lord", category: "milestone", description: "500 hours of total agent active time.", badge: null },
|
|
94
|
+
{ id: "eternal-grind", name: "Eternal Grind", category: "milestone", description: "2,000 hours of total agent active time.", badge: null },
|
|
95
|
+
{ id: "epoch", name: "Epoch", category: "milestone", description: "5,000 hours of total agent active time.", badge: null },
|
|
96
|
+
{ id: "old-growth", name: "Old Growth", category: "milestone", description: "Companion is 14 days old.", badge: null },
|
|
97
|
+
{ id: "seasoned", name: "Seasoned", category: "milestone", description: "Companion is 90 days old.", badge: null },
|
|
98
|
+
{ id: "ancient", name: "Ancient", category: "milestone", description: "Companion is 365 days old.", badge: null },
|
|
99
|
+
{ id: "apprentice", name: "Apprentice", category: "milestone", description: "Reach level 5.", badge: null },
|
|
100
|
+
{ id: "journeyman", name: "Journeyman", category: "milestone", description: "Reach level 15.", badge: null },
|
|
101
|
+
{ id: "master", name: "Master", category: "milestone", description: "Reach level 30.", badge: null },
|
|
102
|
+
{ id: "grandmaster", name: "Grandmaster", category: "milestone", description: "Reach level 50.", badge: null },
|
|
103
|
+
// Session (19)
|
|
104
|
+
{ id: "marathon", name: "Marathon", category: "session", description: "Complete a session with 15+ agents.", badge: "~^~" },
|
|
105
|
+
{ id: "squad", name: "Squad Up", category: "session", description: "Complete a session with 10+ agents.", badge: null },
|
|
106
|
+
{ id: "battalion", name: "Battalion", category: "session", description: "Complete a session with 25+ agents.", badge: null },
|
|
107
|
+
{ id: "swarm", name: "The Swarm", category: "session", description: "Complete a session with 50+ agents.", badge: null },
|
|
108
|
+
{ id: "blitz", name: "Blitz", category: "session", description: "Complete a session in under 5 minutes.", badge: null },
|
|
109
|
+
{ id: "speed-run", name: "Speed Run", category: "session", description: "Complete a session in under 15 minutes.", badge: null },
|
|
110
|
+
{ id: "flash", name: "Flash", category: "session", description: "Complete a session in under 2 minutes.", badge: null },
|
|
111
|
+
{ id: "flawless", name: "Flawless", category: "session", description: "Complete a session with 10+ agents and zero crashes.", badge: "*" },
|
|
112
|
+
{ id: "speed-demon", name: "Speed Demon", category: "session", description: "10 consecutive sessions completing in 3 or fewer cycles.", badge: "\u26A1" },
|
|
113
|
+
{ id: "iron-will", name: "Iron Will", category: "session", description: "5 consecutive sessions each with 8+ orchestrator cycles.", badge: "[]" },
|
|
114
|
+
{ id: "glass-cannon", name: "Glass Cannon", category: "session", description: "5+ agents, all crashed, but session completed anyway.", badge: null },
|
|
115
|
+
{ id: "solo", name: "Solo", category: "session", description: "Complete a session with exactly one agent.", badge: null },
|
|
116
|
+
{ id: "one-more-cycle", name: "One More Cycle", category: "session", description: "A session with 10+ orchestrator cycles.", badge: null },
|
|
117
|
+
{ id: "deep-dive", name: "Deep Dive", category: "session", description: "A session with 15+ orchestrator cycles.", badge: null },
|
|
118
|
+
{ id: "abyss", name: "Into the Abyss", category: "session", description: "A session with 25+ orchestrator cycles.", badge: null },
|
|
119
|
+
{ id: "eternal-recurrence", name: "Eternal Recurrence", category: "session", description: "A session with 40+ orchestrator cycles.", badge: null },
|
|
120
|
+
{ id: "endurance", name: "Endurance", category: "session", description: "A single session running 4+ hours.", badge: null },
|
|
121
|
+
{ id: "ultramarathon", name: "Ultramarathon", category: "session", description: "A single session running 6+ hours.", badge: null },
|
|
122
|
+
{ id: "one-shot", name: "One Shot", category: "session", description: "Complete with 5+ agents in exactly 1 orchestrator cycle.", badge: null },
|
|
123
|
+
{ id: "quick-draw", name: "Quick Draw", category: "session", description: "First agent spawned within 20s of session start.", badge: null },
|
|
124
|
+
// Time (6)
|
|
125
|
+
{ id: "night-owl", name: "Night Owl", category: "time", description: "Complete a session started between 1am and 5am.", badge: ")" },
|
|
126
|
+
{ id: "dawn-patrol", name: "Dawn Patrol", category: "time", description: "Session running 3+ hours that spans midnight to 6am.", badge: null },
|
|
127
|
+
{ id: "early-bird", name: "Early Bird", category: "time", description: "Start a session before 6am.", badge: null },
|
|
128
|
+
{ id: "weekend-warrior", name: "Weekend Warrior", category: "time", description: "Complete a session on a Saturday or Sunday.", badge: null },
|
|
129
|
+
{ id: "all-nighter", name: "All-Nighter", category: "time", description: "Single session running 5+ hours.", badge: null },
|
|
130
|
+
{ id: "witching-hour", name: "Witching Hour", category: "time", description: "Start a session between 3am and 4am.", badge: null },
|
|
131
|
+
// Behavioral (16)
|
|
132
|
+
{ id: "sisyphean", name: "Sisyphean", category: "behavioral", description: "Restart the same task 3+ times.", badge: ";" },
|
|
133
|
+
{ id: "stubborn", name: "Stubborn", category: "behavioral", description: "Restart the same task 5+ times and eventually complete it.", badge: null },
|
|
134
|
+
{ id: "one-must-imagine", name: "One Must Imagine", category: "behavioral", description: "Restart the same task 10+ times.", badge: null },
|
|
135
|
+
{ id: "creature-of-habit", name: "Creature of Habit", category: "behavioral", description: "Visit the same repo 10 times.", badge: null },
|
|
136
|
+
{ id: "loyal", name: "Loyal", category: "behavioral", description: "Visit the same repo 30 times.", badge: null },
|
|
137
|
+
{ id: "wanderer", name: "Wanderer", category: "behavioral", description: "3+ different repos in a single calendar day.", badge: null },
|
|
138
|
+
{ id: "streak", name: "Streak", category: "behavioral", description: "7 consecutive days with at least one session.", badge: null },
|
|
139
|
+
{ id: "iron-streak", name: "Iron Streak", category: "behavioral", description: "14 consecutive days with at least one session.", badge: null },
|
|
140
|
+
{ id: "hot-streak", name: "Hot Streak", category: "behavioral", description: "15 consecutive clean sessions.", badge: null },
|
|
141
|
+
{ id: "momentum", name: "Momentum", category: "behavioral", description: "5 sessions completed within 4 hours.", badge: null },
|
|
142
|
+
{ id: "overdrive", name: "Overdrive", category: "behavioral", description: "Complete 6+ sessions in a single calendar day.", badge: null },
|
|
143
|
+
{ id: "patient-one", name: "Patient One", category: "behavioral", description: "Idle 30+ minutes between cycles in a session.", badge: null },
|
|
144
|
+
{ id: "message-in-a-bottle", name: "Message in a Bottle", category: "behavioral", description: "10+ messages sent to a single session.", badge: null },
|
|
145
|
+
{ id: "deep-conversation", name: "Deep Conversation", category: "behavioral", description: "Send 20+ messages to a single session.", badge: null },
|
|
146
|
+
{ id: "comeback-kid", name: "Comeback Kid", category: "behavioral", description: "Resume a paused/killed session and complete it.", badge: null },
|
|
147
|
+
{ id: "pair-programming", name: "Pair Programming", category: "behavioral", description: "8+ user messages during a single active session.", badge: null }
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// src/daemon/companion-memory.ts
|
|
151
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
152
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
153
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
154
|
+
import { z } from "zod";
|
|
155
|
+
|
|
156
|
+
// src/daemon/haiku.ts
|
|
157
|
+
import { query, createSdkMcpServer } from "@r-cli/sdk";
|
|
158
|
+
var COOLDOWN_MS = 5 * 60 * 1e3;
|
|
159
|
+
var disabledUntil = 0;
|
|
160
|
+
var disabledUntilTools = 0;
|
|
161
|
+
function applyAuthCooldown(err, target) {
|
|
162
|
+
const status = err?.status;
|
|
163
|
+
if (status === 401 || status === 403) {
|
|
164
|
+
if (target === "main") disabledUntil = Date.now() + COOLDOWN_MS;
|
|
165
|
+
else disabledUntilTools = Date.now() + COOLDOWN_MS;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
async function callHaiku(prompt, systemPrompt) {
|
|
169
|
+
if (Date.now() < disabledUntil) return null;
|
|
170
|
+
try {
|
|
171
|
+
const session = await query({
|
|
172
|
+
prompt,
|
|
173
|
+
options: {
|
|
174
|
+
model: "haiku",
|
|
175
|
+
maxTurns: 1,
|
|
176
|
+
env: execEnv(),
|
|
177
|
+
...systemPrompt ? { systemPrompt } : {}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
let text = "";
|
|
181
|
+
for await (const msg of session) {
|
|
182
|
+
if (msg.type === "assistant" && msg.message?.content) {
|
|
183
|
+
for (const block of msg.message.content) {
|
|
184
|
+
if (block.type === "text") text += block.text;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return text.trim() || null;
|
|
189
|
+
} catch (err) {
|
|
190
|
+
console.error(`[sisyphus] Haiku call failed: ${err instanceof Error ? err.message : err}`);
|
|
191
|
+
applyAuthCooldown(err, "main");
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
async function callHaikuWithTools(opts) {
|
|
196
|
+
if (Date.now() < disabledUntilTools) {
|
|
197
|
+
return { ok: false, error: "haiku tool path on cooldown" };
|
|
198
|
+
}
|
|
199
|
+
const server = createSdkMcpServer({
|
|
200
|
+
name: opts.mcpServerName,
|
|
201
|
+
version: "1.0.0",
|
|
202
|
+
tools: opts.customTools
|
|
203
|
+
});
|
|
204
|
+
const allowedTools = opts.customTools.map((t) => `mcp__${opts.mcpServerName}__${t.name}`);
|
|
205
|
+
let turns = 0;
|
|
206
|
+
try {
|
|
207
|
+
const session = query({
|
|
208
|
+
prompt: opts.userPrompt,
|
|
209
|
+
options: {
|
|
210
|
+
model: "haiku",
|
|
211
|
+
maxTurns: opts.maxTurns ?? 5,
|
|
212
|
+
cwd: opts.cwd,
|
|
213
|
+
env: execEnv(),
|
|
214
|
+
systemPrompt: opts.systemPrompt,
|
|
215
|
+
mcpServers: { [opts.mcpServerName]: server },
|
|
216
|
+
tools: [],
|
|
217
|
+
allowedTools,
|
|
218
|
+
canUseTool: async () => ({ behavior: "allow" })
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
for await (const msg of session) {
|
|
222
|
+
if (msg.type === "result") {
|
|
223
|
+
turns = msg.num_turns ?? turns;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return { ok: true, turns };
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.error(`[sisyphus] callHaikuWithTools failed: ${err instanceof Error ? err.message : err}`);
|
|
229
|
+
applyAuthCooldown(err, "tools");
|
|
230
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
async function callHaikuStructured(prompt, jsonSchema, zodSchema) {
|
|
234
|
+
if (Date.now() < disabledUntil) return null;
|
|
235
|
+
try {
|
|
236
|
+
const session = await query({
|
|
237
|
+
prompt,
|
|
238
|
+
options: {
|
|
239
|
+
model: "haiku",
|
|
240
|
+
maxTurns: 2,
|
|
241
|
+
env: execEnv(),
|
|
242
|
+
outputFormat: {
|
|
243
|
+
type: "json_schema",
|
|
244
|
+
schema: jsonSchema
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
let result = void 0;
|
|
249
|
+
for await (const msg of session) {
|
|
250
|
+
if (msg.type === "result" && msg.subtype === "success" && msg.structured_output !== void 0) {
|
|
251
|
+
result = msg.structured_output;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (result === void 0) return null;
|
|
255
|
+
const parsed = zodSchema.safeParse(result);
|
|
256
|
+
return parsed.success ? parsed.data : null;
|
|
257
|
+
} catch (err) {
|
|
258
|
+
console.error(`[sisyphus] Haiku structured call failed: ${err instanceof Error ? err.message : err}`);
|
|
259
|
+
applyAuthCooldown(err, "main");
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/daemon/companion.ts
|
|
265
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
266
|
+
import { randomUUID } from "crypto";
|
|
267
|
+
import { dirname, join } from "path";
|
|
268
|
+
var MIN_SAMPLES = 5;
|
|
269
|
+
var MIN_STDDEV_RATIO = 0.2;
|
|
270
|
+
var ABSOLUTE_STDDEV_FLOORS = {
|
|
271
|
+
sessionMs: 3e5,
|
|
272
|
+
// 5 minutes
|
|
273
|
+
cycleCount: 1,
|
|
274
|
+
agentCount: 1,
|
|
275
|
+
sessionsPerDay: 0.5,
|
|
276
|
+
recentAgentThroughput: 2
|
|
277
|
+
};
|
|
278
|
+
var COLD_START_DEFAULTS = {
|
|
279
|
+
sessionMs: { mean: 36e5, stddev: 24e5 },
|
|
280
|
+
cycleCount: { mean: 5, stddev: 3 },
|
|
281
|
+
agentCount: { mean: 5, stddev: 4 },
|
|
282
|
+
sessionsPerDay: { mean: 3, stddev: 2 },
|
|
283
|
+
recentAgentThroughput: { mean: 8, stddev: 6 }
|
|
284
|
+
};
|
|
285
|
+
function emptyStats() {
|
|
286
|
+
return { count: 0, mean: 0, m2: 0 };
|
|
287
|
+
}
|
|
288
|
+
function defaultBaselines() {
|
|
289
|
+
return {
|
|
290
|
+
sessionMs: emptyStats(),
|
|
291
|
+
cycleCount: emptyStats(),
|
|
292
|
+
agentCount: emptyStats(),
|
|
293
|
+
sessionsPerDay: emptyStats(),
|
|
294
|
+
recentAgentThroughput: emptyStats(),
|
|
295
|
+
lastCountedDay: null,
|
|
296
|
+
pendingDayCount: 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function welfordUpdate(stats, value) {
|
|
300
|
+
stats.count++;
|
|
301
|
+
const delta = value - stats.mean;
|
|
302
|
+
stats.mean += delta / stats.count;
|
|
303
|
+
const delta2 = value - stats.mean;
|
|
304
|
+
stats.m2 += delta * delta2;
|
|
305
|
+
}
|
|
306
|
+
function zScore(value, stats, metric) {
|
|
307
|
+
const defaults = COLD_START_DEFAULTS[metric];
|
|
308
|
+
const floor = ABSOLUTE_STDDEV_FLOORS[metric];
|
|
309
|
+
if (stats.count < MIN_SAMPLES) {
|
|
310
|
+
if (defaults.stddev === 0) return 0;
|
|
311
|
+
return (value - defaults.mean) / defaults.stddev;
|
|
312
|
+
}
|
|
313
|
+
const rawStddev = stats.count >= 2 ? Math.sqrt(stats.m2 / stats.count) : 0;
|
|
314
|
+
const stddev = Math.max(rawStddev, stats.mean * MIN_STDDEV_RATIO, floor);
|
|
315
|
+
if (stddev === 0) return 0;
|
|
316
|
+
return (value - stats.mean) / stddev;
|
|
317
|
+
}
|
|
318
|
+
function loadCompanion() {
|
|
319
|
+
const path = companionPath();
|
|
320
|
+
if (!existsSync(path)) {
|
|
321
|
+
const state2 = createDefaultCompanion();
|
|
322
|
+
saveCompanion(state2);
|
|
323
|
+
return state2;
|
|
324
|
+
}
|
|
325
|
+
const raw = readFileSync(path, "utf-8");
|
|
326
|
+
const state = JSON.parse(raw);
|
|
327
|
+
if (state.consecutiveCleanSessions == null) state.consecutiveCleanSessions = 0;
|
|
328
|
+
if (state.consecutiveDaysActive == null) state.consecutiveDaysActive = 0;
|
|
329
|
+
if (state.lastActiveDate == null) state.lastActiveDate = null;
|
|
330
|
+
if (state.taskHistory == null) state.taskHistory = {};
|
|
331
|
+
if (state.dailyRepos == null) state.dailyRepos = {};
|
|
332
|
+
if (state.recentCompletions == null) state.recentCompletions = [];
|
|
333
|
+
if (state.lifetimeAgentsSpawned == null) state.lifetimeAgentsSpawned = 0;
|
|
334
|
+
if (state.consecutiveEfficientSessions == null) state.consecutiveEfficientSessions = 0;
|
|
335
|
+
if (state.consecutiveHighCycleSessions == null) state.consecutiveHighCycleSessions = 0;
|
|
336
|
+
if (state.spinnerVerbIndex == null) state.spinnerVerbIndex = 0;
|
|
337
|
+
if (state.baselines == null) state.baselines = defaultBaselines();
|
|
338
|
+
if (state.baselines.recentAgentThroughput == null) state.baselines.recentAgentThroughput = emptyStats();
|
|
339
|
+
if (state.commentaryHistory == null) state.commentaryHistory = [];
|
|
340
|
+
if (state.feedbackHistory == null) state.feedbackHistory = [];
|
|
341
|
+
return state;
|
|
342
|
+
}
|
|
343
|
+
function saveCompanion(state) {
|
|
344
|
+
const path = companionPath();
|
|
345
|
+
const dir = dirname(path);
|
|
346
|
+
mkdirSync(dir, { recursive: true });
|
|
347
|
+
const tmp = join(dir, `.companion.${randomUUID()}.tmp`);
|
|
348
|
+
writeFileSync(tmp, JSON.stringify(state, null, 2), "utf-8");
|
|
349
|
+
renameSync(tmp, path);
|
|
350
|
+
}
|
|
351
|
+
var MAX_COMMENTARY_HISTORY = 1e3;
|
|
352
|
+
var MAX_FEEDBACK_HISTORY = 1e3;
|
|
353
|
+
function recordCommentary(companion, text, event) {
|
|
354
|
+
const entry = { text, event, timestamp: (/* @__PURE__ */ new Date()).toISOString() };
|
|
355
|
+
companion.lastCommentary = entry;
|
|
356
|
+
if (!companion.commentaryHistory) companion.commentaryHistory = [];
|
|
357
|
+
companion.commentaryHistory.push(entry);
|
|
358
|
+
if (companion.commentaryHistory.length > MAX_COMMENTARY_HISTORY) {
|
|
359
|
+
companion.commentaryHistory = companion.commentaryHistory.slice(-MAX_COMMENTARY_HISTORY);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
function recordFeedback(companion, text, rating, event, comment) {
|
|
363
|
+
const entry = { commentaryText: text, rating, event, timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...comment != null ? { comment } : {} };
|
|
364
|
+
if (!companion.feedbackHistory) companion.feedbackHistory = [];
|
|
365
|
+
companion.feedbackHistory.push(entry);
|
|
366
|
+
if (companion.feedbackHistory.length > MAX_FEEDBACK_HISTORY) {
|
|
367
|
+
companion.feedbackHistory = companion.feedbackHistory.slice(-MAX_FEEDBACK_HISTORY);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function createDefaultCompanion() {
|
|
371
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
372
|
+
return {
|
|
373
|
+
version: 1,
|
|
374
|
+
name: null,
|
|
375
|
+
createdAt: now,
|
|
376
|
+
stats: {
|
|
377
|
+
strength: 0,
|
|
378
|
+
endurance: 0,
|
|
379
|
+
wisdom: 0,
|
|
380
|
+
patience: 0
|
|
381
|
+
},
|
|
382
|
+
xp: 0,
|
|
383
|
+
level: 1,
|
|
384
|
+
title: "Boulder Intern",
|
|
385
|
+
mood: "sleepy",
|
|
386
|
+
moodUpdatedAt: now,
|
|
387
|
+
achievements: [],
|
|
388
|
+
repos: {},
|
|
389
|
+
lastCommentary: null,
|
|
390
|
+
commentaryHistory: [],
|
|
391
|
+
sessionsCompleted: 0,
|
|
392
|
+
sessionsCrashed: 0,
|
|
393
|
+
totalActiveMs: 0,
|
|
394
|
+
lifetimeAgentsSpawned: 0,
|
|
395
|
+
consecutiveCleanSessions: 0,
|
|
396
|
+
consecutiveEfficientSessions: 0,
|
|
397
|
+
consecutiveHighCycleSessions: 0,
|
|
398
|
+
consecutiveDaysActive: 0,
|
|
399
|
+
lastActiveDate: null,
|
|
400
|
+
taskHistory: {},
|
|
401
|
+
dailyRepos: {},
|
|
402
|
+
recentCompletions: [],
|
|
403
|
+
spinnerVerbIndex: 0,
|
|
404
|
+
baselines: defaultBaselines(),
|
|
405
|
+
feedbackHistory: []
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function computeXP(stats) {
|
|
409
|
+
const strengthXP = stats.strength * 50;
|
|
410
|
+
const enduranceXP = stats.endurance / 36e5 * 20;
|
|
411
|
+
const wisdomXP = stats.wisdom * 40;
|
|
412
|
+
const patienceXP = stats.patience * 8;
|
|
413
|
+
return Math.floor(strengthXP + enduranceXP + wisdomXP + patienceXP);
|
|
414
|
+
}
|
|
415
|
+
function computeStrengthGain(agentCount) {
|
|
416
|
+
if (agentCount <= 0) return 0;
|
|
417
|
+
if (agentCount <= 2) return 1;
|
|
418
|
+
if (agentCount <= 5) return 2;
|
|
419
|
+
if (agentCount <= 10) return 3;
|
|
420
|
+
if (agentCount <= 20) return 4;
|
|
421
|
+
return 5;
|
|
422
|
+
}
|
|
423
|
+
function computeLevel(xp) {
|
|
424
|
+
let level = 1;
|
|
425
|
+
let threshold = 150;
|
|
426
|
+
let cumulative = 0;
|
|
427
|
+
while (cumulative + threshold <= xp) {
|
|
428
|
+
cumulative += threshold;
|
|
429
|
+
level++;
|
|
430
|
+
threshold = Math.floor(threshold * 1.35);
|
|
431
|
+
}
|
|
432
|
+
return level;
|
|
433
|
+
}
|
|
434
|
+
function computeLevelProgress(xp) {
|
|
435
|
+
let threshold = 150;
|
|
436
|
+
let cumulative = 0;
|
|
437
|
+
while (cumulative + threshold <= xp) {
|
|
438
|
+
cumulative += threshold;
|
|
439
|
+
threshold = Math.floor(threshold * 1.35);
|
|
440
|
+
}
|
|
441
|
+
return { xpIntoLevel: xp - cumulative, xpForNextLevel: threshold };
|
|
442
|
+
}
|
|
443
|
+
var TITLE_MAP = {
|
|
444
|
+
1: "Boulder Intern",
|
|
445
|
+
2: "Pebble Pusher",
|
|
446
|
+
3: "Rock Hauler",
|
|
447
|
+
4: "Gravel Wrangler",
|
|
448
|
+
5: "Slope Familiar",
|
|
449
|
+
6: "Incline Regular",
|
|
450
|
+
7: "Ridge Runner",
|
|
451
|
+
8: "Crag Warden",
|
|
452
|
+
9: "Stone Whisperer",
|
|
453
|
+
10: "Boulder Brother",
|
|
454
|
+
11: "Hill Veteran",
|
|
455
|
+
12: "Summit Aspirant",
|
|
456
|
+
13: "Peak Haunter",
|
|
457
|
+
14: "Cliff Sage",
|
|
458
|
+
15: "Mountain's Shadow",
|
|
459
|
+
16: "Eternal Roller",
|
|
460
|
+
17: "Gravity's Rival",
|
|
461
|
+
18: "The Unmoved Mover",
|
|
462
|
+
19: "Camus Was Right",
|
|
463
|
+
20: "The Absurd Hero",
|
|
464
|
+
25: "One Must Imagine Him Happy",
|
|
465
|
+
30: "He Has Always Been Here"
|
|
466
|
+
};
|
|
467
|
+
function getTitle(level) {
|
|
468
|
+
for (let l = level; l >= 1; l--) {
|
|
469
|
+
if (TITLE_MAP[l] !== void 0) return TITLE_MAP[l];
|
|
470
|
+
}
|
|
471
|
+
return "Boulder Intern";
|
|
472
|
+
}
|
|
473
|
+
function computeMood(companion, session, signals) {
|
|
474
|
+
if (!signals) {
|
|
475
|
+
const hour = (/* @__PURE__ */ new Date()).getHours();
|
|
476
|
+
if (hour >= 2 && hour < 6) return "existential";
|
|
477
|
+
if (hour >= 22 || hour < 2) return "sleepy";
|
|
478
|
+
return "zen";
|
|
479
|
+
}
|
|
480
|
+
const scores = {
|
|
481
|
+
happy: 0,
|
|
482
|
+
grinding: 0,
|
|
483
|
+
frustrated: 0,
|
|
484
|
+
zen: 0,
|
|
485
|
+
sleepy: 0,
|
|
486
|
+
excited: 0,
|
|
487
|
+
existential: 0
|
|
488
|
+
};
|
|
489
|
+
const cycleCount = signals.cycleCount ?? 0;
|
|
490
|
+
const sessionsCompletedToday = signals.sessionsCompletedToday ?? 0;
|
|
491
|
+
const baselines = companion.baselines ?? defaultBaselines();
|
|
492
|
+
const sessionZ = zScore(signals.sessionLengthMs, baselines.sessionMs, "sessionMs");
|
|
493
|
+
const cycleZ = zScore(cycleCount, baselines.cycleCount, "cycleCount");
|
|
494
|
+
const agentZ = zScore(signals.totalAgentCount ?? 0, baselines.agentCount, "agentCount");
|
|
495
|
+
const dailyZ = zScore(sessionsCompletedToday, baselines.sessionsPerDay, "sessionsPerDay");
|
|
496
|
+
const recentAgentZ = zScore(signals.recentAgentCount ?? 0, baselines.recentAgentThroughput, "recentAgentThroughput");
|
|
497
|
+
if (signals.justCompleted) scores.happy += 50;
|
|
498
|
+
if (signals.justCompleted && sessionZ < -0.5) scores.happy += 15;
|
|
499
|
+
if (dailyZ > 0.5) scores.happy += 20;
|
|
500
|
+
if (dailyZ > 1.5) scores.happy += 10;
|
|
501
|
+
if (signals.hourOfDay >= 6 && signals.hourOfDay < 12) scores.happy += 15;
|
|
502
|
+
if (signals.hourOfDay >= 12 && signals.hourOfDay < 17) scores.happy += 8;
|
|
503
|
+
if ((signals.activeAgentCount ?? 0) >= 1 && sessionZ < -0.5) scores.happy += 12;
|
|
504
|
+
const lastCompletion = companion.recentCompletions.length > 0 ? Date.now() - new Date(companion.recentCompletions[companion.recentCompletions.length - 1]).getTime() : Infinity;
|
|
505
|
+
if (lastCompletion < 18e5 && signals.sessionLengthMs > 0) scores.happy += 20;
|
|
506
|
+
if (sessionZ > 0.5) scores.grinding += 15;
|
|
507
|
+
if (sessionZ > 1) scores.grinding += 10;
|
|
508
|
+
if (sessionZ > 1.5) scores.grinding += 8;
|
|
509
|
+
if (recentAgentZ > 0.5) scores.grinding += 12;
|
|
510
|
+
if (recentAgentZ > 1) scores.grinding += 10;
|
|
511
|
+
if (recentAgentZ > 1.5) scores.grinding += 8;
|
|
512
|
+
if (cycleZ > 0.5) scores.grinding += 8;
|
|
513
|
+
const rollbacks = signals.rollbackCount ?? 0;
|
|
514
|
+
const restartedAgents = signals.restartedAgentCount ?? 0;
|
|
515
|
+
const lostAgents = signals.lostAgentCount ?? 0;
|
|
516
|
+
const killedAgents = signals.killedAgentCount ?? 0;
|
|
517
|
+
if (signals.justCrashed) scores.frustrated += 30;
|
|
518
|
+
if (signals.recentCrashes >= 2) scores.frustrated += 20;
|
|
519
|
+
if (signals.recentCrashes >= 4) scores.frustrated += 15;
|
|
520
|
+
if (rollbacks >= 1) scores.frustrated += 25;
|
|
521
|
+
if (rollbacks >= 3) scores.frustrated += 25;
|
|
522
|
+
if (restartedAgents >= 1) scores.frustrated += 15;
|
|
523
|
+
if (restartedAgents >= 3) scores.frustrated += 15;
|
|
524
|
+
if (lostAgents >= 1) scores.frustrated += 15;
|
|
525
|
+
if (lostAgents >= 3) scores.frustrated += 10;
|
|
526
|
+
if (killedAgents >= 2) scores.frustrated += 10;
|
|
527
|
+
if (sessionZ > 1.5 && (signals.recentCrashes > 0 || rollbacks > 0)) scores.frustrated += 12;
|
|
528
|
+
if (companion.stats.patience > 30) scores.zen += 15;
|
|
529
|
+
if (signals.idleDurationMs > 12e4 && signals.idleDurationMs <= 9e5) scores.zen += 25;
|
|
530
|
+
if (sessionZ > -1 && sessionZ < 0.3) scores.zen += 15;
|
|
531
|
+
if (cycleZ < 0) scores.zen += 10;
|
|
532
|
+
if (signals.hourOfDay >= 6 && signals.hourOfDay < 10 && (signals.activeAgentCount ?? 0) === 0) scores.zen += 10;
|
|
533
|
+
if (dailyZ >= -0.5 && dailyZ <= 0.5) scores.zen += 10;
|
|
534
|
+
if (signals.idleDurationMs > 9e5) scores.sleepy += 30;
|
|
535
|
+
if (signals.idleDurationMs > 27e5) scores.sleepy += 25;
|
|
536
|
+
if (signals.idleDurationMs > 54e5) scores.sleepy += 15;
|
|
537
|
+
if (signals.hourOfDay >= 22 || signals.hourOfDay < 6) scores.sleepy += 20;
|
|
538
|
+
if (signals.idleDurationMs > 3e5 && (signals.hourOfDay >= 22 || signals.hourOfDay < 6)) scores.sleepy += 15;
|
|
539
|
+
if (sessionZ > 2.5) scores.sleepy += 12;
|
|
540
|
+
if (signals.justLeveledUp) scores.excited += 60;
|
|
541
|
+
if (signals.justCompleted && agentZ > 1) scores.excited += 30;
|
|
542
|
+
if (agentZ > 1.5) scores.excited += 20;
|
|
543
|
+
if (agentZ > 2) scores.excited += 15;
|
|
544
|
+
if (signals.justCompleted && sessionZ < -1) scores.excited += 20;
|
|
545
|
+
if (signals.hourOfDay >= 2 && signals.hourOfDay < 6) scores.existential += 25;
|
|
546
|
+
if (signals.hourOfDay >= 0 && signals.hourOfDay < 2) scores.existential += 10;
|
|
547
|
+
const enduranceHours = companion.stats.endurance / 36e5;
|
|
548
|
+
if (enduranceHours > 40) scores.existential += 15;
|
|
549
|
+
if (signals.hourOfDay >= 2 && signals.hourOfDay < 6 && enduranceHours > 40) {
|
|
550
|
+
scores.existential += 25;
|
|
551
|
+
}
|
|
552
|
+
const now = Date.now();
|
|
553
|
+
const weekAgo = now - 7 * 24 * 36e5;
|
|
554
|
+
const weeklyCompletions = companion.recentCompletions.filter(
|
|
555
|
+
(ts) => new Date(ts).getTime() > weekAgo
|
|
556
|
+
).length;
|
|
557
|
+
const weeklyAvgDaily = weeklyCompletions / 7;
|
|
558
|
+
const weeklyZ = zScore(weeklyAvgDaily, baselines.sessionsPerDay, "sessionsPerDay");
|
|
559
|
+
if (weeklyZ > 0.5) scores.existential += 8;
|
|
560
|
+
if (weeklyZ > 1) scores.existential += 7;
|
|
561
|
+
if (weeklyZ > 1.5) scores.existential += 5;
|
|
562
|
+
const consecutiveDays = companion.consecutiveDaysActive ?? 0;
|
|
563
|
+
if (consecutiveDays >= 3) scores.existential += Math.min(20, (consecutiveDays - 2) * 3);
|
|
564
|
+
const goodRatings = signals.recentFeedbackGood ?? 0;
|
|
565
|
+
const badRatings = signals.recentFeedbackBad ?? 0;
|
|
566
|
+
const whipRatings = signals.recentFeedbackWhip ?? 0;
|
|
567
|
+
if (goodRatings > 0) {
|
|
568
|
+
scores.happy += goodRatings * 20;
|
|
569
|
+
scores.excited += goodRatings * 8;
|
|
570
|
+
scores.frustrated = Math.max(0, scores.frustrated - goodRatings * 10);
|
|
571
|
+
}
|
|
572
|
+
if (badRatings > 0) {
|
|
573
|
+
scores.happy = Math.max(0, scores.happy - badRatings * 15);
|
|
574
|
+
scores.frustrated += badRatings * 20;
|
|
575
|
+
scores.zen = Math.max(0, scores.zen - badRatings * 10);
|
|
576
|
+
}
|
|
577
|
+
if (whipRatings > 0) {
|
|
578
|
+
scores.happy = Math.max(0, scores.happy - whipRatings * 15);
|
|
579
|
+
scores.sleepy = Math.max(0, scores.sleepy - whipRatings * 15);
|
|
580
|
+
scores.zen = Math.max(0, scores.zen - whipRatings * 15);
|
|
581
|
+
scores.frustrated = Math.max(0, scores.frustrated - whipRatings * 8);
|
|
582
|
+
scores.existential += whipRatings * 25;
|
|
583
|
+
}
|
|
584
|
+
const moodOrder = ["happy", "grinding", "frustrated", "zen", "sleepy", "excited", "existential"];
|
|
585
|
+
let best = "grinding";
|
|
586
|
+
let bestScore = -1;
|
|
587
|
+
for (const mood of moodOrder) {
|
|
588
|
+
if (scores[mood] > bestScore) {
|
|
589
|
+
bestScore = scores[mood];
|
|
590
|
+
best = mood;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
companion.debugMood = { signals, scores: { ...scores }, winner: best };
|
|
594
|
+
return best;
|
|
595
|
+
}
|
|
596
|
+
function daysSince(isoTimestamp) {
|
|
597
|
+
return (Date.now() - new Date(isoTimestamp).getTime()) / (1e3 * 60 * 60 * 24);
|
|
598
|
+
}
|
|
599
|
+
var ACHIEVEMENT_CHECKERS = {
|
|
600
|
+
// Milestone
|
|
601
|
+
"first-blood": (c) => c.sessionsCompleted >= 1,
|
|
602
|
+
"regular": (c) => c.sessionsCompleted >= 10,
|
|
603
|
+
"centurion": (c) => c.sessionsCompleted >= 100,
|
|
604
|
+
"veteran": (c) => c.sessionsCompleted >= 500,
|
|
605
|
+
"thousand-boulder": (c) => c.sessionsCompleted >= 1e3,
|
|
606
|
+
"cartographer": (c) => Object.keys(c.repos).length >= 5,
|
|
607
|
+
"world-traveler": (c) => Object.keys(c.repos).length >= 15,
|
|
608
|
+
"omnipresent": (c) => Object.keys(c.repos).length >= 30,
|
|
609
|
+
"swarm-starter": (c) => c.lifetimeAgentsSpawned >= 50,
|
|
610
|
+
"hive-mind": (c) => c.lifetimeAgentsSpawned >= 500,
|
|
611
|
+
"legion": (c) => c.lifetimeAgentsSpawned >= 2e3,
|
|
612
|
+
"army-of-thousands": (c) => c.lifetimeAgentsSpawned >= 5e3,
|
|
613
|
+
"singularity": (c) => c.lifetimeAgentsSpawned >= 1e4,
|
|
614
|
+
"first-shift": (c) => c.totalActiveMs >= 36e6,
|
|
615
|
+
"workaholic": (c) => c.totalActiveMs >= 36e7,
|
|
616
|
+
"time-lord": (c) => c.totalActiveMs >= 18e8,
|
|
617
|
+
"eternal-grind": (c) => c.totalActiveMs >= 72e8,
|
|
618
|
+
"epoch": (c) => c.totalActiveMs >= 18e9,
|
|
619
|
+
"old-growth": (c) => daysSince(c.createdAt) >= 14,
|
|
620
|
+
"seasoned": (c) => daysSince(c.createdAt) >= 90,
|
|
621
|
+
"ancient": (c) => daysSince(c.createdAt) >= 365,
|
|
622
|
+
"apprentice": (c) => c.level >= 5,
|
|
623
|
+
"journeyman": (c) => c.level >= 15,
|
|
624
|
+
"master": (c) => c.level >= 30,
|
|
625
|
+
"grandmaster": (c) => c.level >= 50,
|
|
626
|
+
// Session
|
|
627
|
+
"marathon": (_c, s) => s != null && s.agents.length >= 15,
|
|
628
|
+
"squad": (_c, s) => s != null && s.agents.length >= 10,
|
|
629
|
+
"battalion": (_c, s) => s != null && s.agents.length >= 25,
|
|
630
|
+
"swarm": (_c, s) => s != null && s.agents.length >= 50,
|
|
631
|
+
"blitz": (_c, s) => s != null && s.activeMs < 3e5 && s.status === "completed",
|
|
632
|
+
"speed-run": (_c, s) => s != null && s.activeMs < 9e5 && s.status === "completed",
|
|
633
|
+
"flash": (_c, s) => s != null && s.activeMs < 12e4 && s.status === "completed",
|
|
634
|
+
"flawless": (_c, s) => s != null && s.agents.length >= 10 && s.status === "completed" && s.agents.every((a) => a.status !== "crashed" && a.status !== "killed"),
|
|
635
|
+
"speed-demon": (c) => c.consecutiveEfficientSessions >= 10,
|
|
636
|
+
"iron-will": (c) => c.consecutiveHighCycleSessions >= 5,
|
|
637
|
+
"glass-cannon": (_c, s) => {
|
|
638
|
+
if (!s || s.status !== "completed" || s.agents.length < 5) return false;
|
|
639
|
+
return s.agents.every((a) => a.status === "crashed" || a.killedReason != null);
|
|
640
|
+
},
|
|
641
|
+
"solo": (_c, s) => s != null && s.status === "completed" && s.agents.length === 1,
|
|
642
|
+
"one-more-cycle": (_c, s) => s != null && s.orchestratorCycles.length >= 10,
|
|
643
|
+
"deep-dive": (_c, s) => s != null && s.orchestratorCycles.length >= 15,
|
|
644
|
+
"abyss": (_c, s) => s != null && s.orchestratorCycles.length >= 25,
|
|
645
|
+
"eternal-recurrence": (_c, s) => s != null && s.orchestratorCycles.length >= 40,
|
|
646
|
+
"endurance": (_c, s) => s != null && s.activeMs >= 144e5,
|
|
647
|
+
"ultramarathon": (_c, s) => s != null && s.activeMs >= 216e5,
|
|
648
|
+
"one-shot": (_c, s) => s != null && s.agents.length >= 5 && s.orchestratorCycles.length === 1 && s.status === "completed",
|
|
649
|
+
"quick-draw": (_c, s) => {
|
|
650
|
+
if (!s || s.agents.length === 0) return false;
|
|
651
|
+
const firstAgent = s.agents[0];
|
|
652
|
+
return new Date(firstAgent.spawnedAt).getTime() - new Date(s.createdAt).getTime() < 2e4;
|
|
653
|
+
},
|
|
654
|
+
// Time
|
|
655
|
+
"night-owl": (_c, s) => {
|
|
656
|
+
if (!s || s.status !== "completed") return false;
|
|
657
|
+
const h = new Date(s.createdAt).getHours();
|
|
658
|
+
return h >= 1 && h < 5;
|
|
659
|
+
},
|
|
660
|
+
"dawn-patrol": (_c, s) => {
|
|
661
|
+
if (!s) return false;
|
|
662
|
+
if (s.activeMs < 108e5) return false;
|
|
663
|
+
const start = new Date(s.createdAt).getTime();
|
|
664
|
+
const end = s.completedAt ? new Date(s.completedAt).getTime() : Date.now();
|
|
665
|
+
const startDate = new Date(start);
|
|
666
|
+
const startHour = startDate.getHours();
|
|
667
|
+
const todayMidnight = new Date(startDate);
|
|
668
|
+
todayMidnight.setHours(0, 0, 0, 0);
|
|
669
|
+
const sixAm = new Date(todayMidnight);
|
|
670
|
+
sixAm.setHours(6, 0, 0, 0);
|
|
671
|
+
if (startHour >= 6) {
|
|
672
|
+
const nextMidnight = new Date(todayMidnight.getTime() + 24 * 60 * 60 * 1e3);
|
|
673
|
+
return start < nextMidnight.getTime() && end > nextMidnight.getTime();
|
|
674
|
+
} else {
|
|
675
|
+
return start < sixAm.getTime();
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
"early-bird": (_c, s) => {
|
|
679
|
+
if (!s) return false;
|
|
680
|
+
return new Date(s.createdAt).getHours() < 6;
|
|
681
|
+
},
|
|
682
|
+
"weekend-warrior": (_c, s) => {
|
|
683
|
+
if (!s || s.status !== "completed") return false;
|
|
684
|
+
const day = new Date(s.completedAt ?? s.createdAt).getDay();
|
|
685
|
+
return day === 0 || day === 6;
|
|
686
|
+
},
|
|
687
|
+
"all-nighter": (_c, s) => s != null && s.activeMs >= 18e6,
|
|
688
|
+
"witching-hour": (_c, s) => {
|
|
689
|
+
if (!s) return false;
|
|
690
|
+
const h = new Date(s.createdAt).getHours();
|
|
691
|
+
return h === 3;
|
|
692
|
+
},
|
|
693
|
+
// Behavioral
|
|
694
|
+
"sisyphean": (c) => Object.values(c.taskHistory).some((v) => v >= 3),
|
|
695
|
+
"stubborn": (c) => Object.values(c.taskHistory).some((v) => v >= 5) && c.sessionsCompleted > 0,
|
|
696
|
+
"one-must-imagine": (c) => Object.values(c.taskHistory).some((v) => v >= 10),
|
|
697
|
+
"creature-of-habit": (c) => Object.values(c.repos).some((r) => r.visits >= 10),
|
|
698
|
+
"loyal": (c) => Object.values(c.repos).some((r) => r.visits >= 30),
|
|
699
|
+
"wanderer": (c) => {
|
|
700
|
+
return Object.values(c.dailyRepos).some((repos) => repos.length >= 3);
|
|
701
|
+
},
|
|
702
|
+
"streak": (c) => c.consecutiveDaysActive >= 7,
|
|
703
|
+
"iron-streak": (c) => c.consecutiveDaysActive >= 14,
|
|
704
|
+
"hot-streak": (c) => c.consecutiveCleanSessions >= 15,
|
|
705
|
+
"momentum": (c) => {
|
|
706
|
+
if (c.recentCompletions.length < 5) return false;
|
|
707
|
+
const last5 = c.recentCompletions.slice(-5);
|
|
708
|
+
const oldest = new Date(last5[0]).getTime();
|
|
709
|
+
const newest = new Date(last5[4]).getTime();
|
|
710
|
+
return newest - oldest <= 4 * 60 * 60 * 1e3;
|
|
711
|
+
},
|
|
712
|
+
"overdrive": (c) => {
|
|
713
|
+
const dateCounts = {};
|
|
714
|
+
for (const ts of c.recentCompletions) {
|
|
715
|
+
const date = ts.slice(0, 10);
|
|
716
|
+
dateCounts[date] = (dateCounts[date] ?? 0) + 1;
|
|
717
|
+
}
|
|
718
|
+
return Object.values(dateCounts).some((count) => count >= 6);
|
|
719
|
+
},
|
|
720
|
+
"patient-one": (_c, s) => {
|
|
721
|
+
if (!s || s.orchestratorCycles.length < 2) return false;
|
|
722
|
+
for (let i = 1; i < s.orchestratorCycles.length; i++) {
|
|
723
|
+
const prev = s.orchestratorCycles[i - 1];
|
|
724
|
+
const curr = s.orchestratorCycles[i];
|
|
725
|
+
if (!prev.completedAt) continue;
|
|
726
|
+
const gap = new Date(curr.timestamp).getTime() - new Date(prev.completedAt).getTime();
|
|
727
|
+
if (gap >= 30 * 60 * 1e3) return true;
|
|
728
|
+
}
|
|
729
|
+
return false;
|
|
730
|
+
},
|
|
731
|
+
"message-in-a-bottle": (_c, s) => {
|
|
732
|
+
if (!s) return false;
|
|
733
|
+
const userMessages = s.messages.filter((m) => m.source.type === "user");
|
|
734
|
+
return userMessages.length >= 10;
|
|
735
|
+
},
|
|
736
|
+
"deep-conversation": (_c, s) => {
|
|
737
|
+
if (!s) return false;
|
|
738
|
+
const userMessages = s.messages.filter((m) => m.source.type === "user");
|
|
739
|
+
return userMessages.length >= 20;
|
|
740
|
+
},
|
|
741
|
+
"comeback-kid": (_c, s) => {
|
|
742
|
+
if (!s || s.status !== "completed") return false;
|
|
743
|
+
return s.orchestratorCycles.length > 0 && s.parentSessionId != null;
|
|
744
|
+
},
|
|
745
|
+
"pair-programming": (_c, s) => {
|
|
746
|
+
if (!s) return false;
|
|
747
|
+
const userMessages = s.messages.filter((m) => m.source.type === "user");
|
|
748
|
+
return userMessages.length >= 8;
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
function checkAchievements(companion, session) {
|
|
752
|
+
const alreadyUnlocked = new Set(companion.achievements.map((a) => a.id));
|
|
753
|
+
const newIds = [];
|
|
754
|
+
for (const [id, checker] of Object.entries(ACHIEVEMENT_CHECKERS)) {
|
|
755
|
+
if (alreadyUnlocked.has(id)) continue;
|
|
756
|
+
if (checker(companion, session)) {
|
|
757
|
+
newIds.push(id);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return newIds;
|
|
761
|
+
}
|
|
762
|
+
var MOOD_SENTIMENT = {
|
|
763
|
+
happy: 0.85,
|
|
764
|
+
excited: 0.9,
|
|
765
|
+
zen: 0.7,
|
|
766
|
+
grinding: 0.45,
|
|
767
|
+
sleepy: 0.4,
|
|
768
|
+
frustrated: 0.15,
|
|
769
|
+
existential: 0.25
|
|
770
|
+
};
|
|
771
|
+
function updateRepoMemory(companion, repoPath, event, activeMs) {
|
|
772
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
773
|
+
const moodScore = MOOD_SENTIMENT[companion.mood] ?? 0.5;
|
|
774
|
+
const existing = companion.repos[repoPath];
|
|
775
|
+
if (!existing) {
|
|
776
|
+
companion.repos[repoPath] = {
|
|
777
|
+
visits: event === "visit" ? 1 : 0,
|
|
778
|
+
completions: event === "completion" ? 1 : 0,
|
|
779
|
+
crashes: event === "crash" ? 1 : 0,
|
|
780
|
+
totalActiveMs: activeMs ?? 0,
|
|
781
|
+
moodAvg: moodScore,
|
|
782
|
+
nickname: null,
|
|
783
|
+
firstSeen: now,
|
|
784
|
+
lastSeen: now
|
|
785
|
+
};
|
|
786
|
+
} else {
|
|
787
|
+
if (event === "visit") existing.visits++;
|
|
788
|
+
if (event === "completion") existing.completions++;
|
|
789
|
+
if (event === "crash") existing.crashes++;
|
|
790
|
+
if (activeMs != null) existing.totalActiveMs += activeMs;
|
|
791
|
+
existing.lastSeen = now;
|
|
792
|
+
const n = existing.visits + existing.completions + existing.crashes;
|
|
793
|
+
existing.moodAvg = existing.moodAvg + (moodScore - existing.moodAvg) / n;
|
|
794
|
+
}
|
|
795
|
+
return companion;
|
|
796
|
+
}
|
|
797
|
+
function recomputeXpLevelTitle(companion) {
|
|
798
|
+
companion.xp = computeXP(companion.stats);
|
|
799
|
+
companion.level = computeLevel(companion.xp);
|
|
800
|
+
companion.title = getTitle(companion.level);
|
|
801
|
+
}
|
|
802
|
+
function todayIso() {
|
|
803
|
+
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
804
|
+
}
|
|
805
|
+
function onSessionStart(companion, cwd) {
|
|
806
|
+
updateRepoMemory(companion, cwd, "visit");
|
|
807
|
+
const today = todayIso();
|
|
808
|
+
if (!companion.dailyRepos[today]) companion.dailyRepos[today] = [];
|
|
809
|
+
if (!companion.dailyRepos[today].includes(cwd)) {
|
|
810
|
+
companion.dailyRepos[today].push(cwd);
|
|
811
|
+
}
|
|
812
|
+
const lastDate = companion.lastActiveDate;
|
|
813
|
+
if (lastDate === null) {
|
|
814
|
+
companion.consecutiveDaysActive = 1;
|
|
815
|
+
} else if (lastDate === today) {
|
|
816
|
+
} else {
|
|
817
|
+
const yesterday = new Date(Date.now() - 864e5).toISOString().slice(0, 10);
|
|
818
|
+
if (lastDate === yesterday) {
|
|
819
|
+
companion.consecutiveDaysActive++;
|
|
820
|
+
} else {
|
|
821
|
+
companion.consecutiveDaysActive = 1;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
companion.lastActiveDate = today;
|
|
825
|
+
recomputeXpLevelTitle(companion);
|
|
826
|
+
}
|
|
827
|
+
function computeWisdomGain(session) {
|
|
828
|
+
let wisdom = 0;
|
|
829
|
+
const totalAgents = session.agents.length;
|
|
830
|
+
const totalCycles = session.orchestratorCycles?.length ?? 0;
|
|
831
|
+
if (totalAgents === 0 || totalCycles === 0) return 0;
|
|
832
|
+
const cleanCompletions = session.agents.filter((a) => a.status === "completed").length;
|
|
833
|
+
if (cleanCompletions / totalAgents >= 0.8) wisdom++;
|
|
834
|
+
if (totalAgents / totalCycles >= 2) wisdom++;
|
|
835
|
+
const modes = new Set((session.orchestratorCycles ?? []).map((c) => c.mode).filter(Boolean));
|
|
836
|
+
if (modes.size >= 2) wisdom++;
|
|
837
|
+
return wisdom;
|
|
838
|
+
}
|
|
839
|
+
function onSessionComplete(companion, session) {
|
|
840
|
+
const creditedCycles = session.companionCreditedCycles ?? 0;
|
|
841
|
+
const creditedActiveMs = session.companionCreditedActiveMs ?? 0;
|
|
842
|
+
const totalCycles = session.orchestratorCycles?.length ?? 0;
|
|
843
|
+
const deltaCycles = Math.max(0, totalCycles - creditedCycles);
|
|
844
|
+
const deltaActiveMs = Math.max(0, session.activeMs - creditedActiveMs);
|
|
845
|
+
companion.sessionsCompleted++;
|
|
846
|
+
companion.totalActiveMs += deltaActiveMs;
|
|
847
|
+
companion.stats.endurance += deltaActiveMs;
|
|
848
|
+
const creditedStrength = session.companionCreditedStrength ?? 0;
|
|
849
|
+
const totalStrength = computeStrengthGain(session.agents.length);
|
|
850
|
+
companion.stats.strength += Math.max(0, totalStrength - creditedStrength);
|
|
851
|
+
const patienceFromCycles = Math.ceil(Math.sqrt(totalCycles)) - Math.ceil(Math.sqrt(creditedCycles));
|
|
852
|
+
companion.stats.patience += Math.max(0, patienceFromCycles);
|
|
853
|
+
const allModes = new Set((session.orchestratorCycles ?? []).map((c) => c.mode));
|
|
854
|
+
const creditedModesCycles = (session.orchestratorCycles ?? []).slice(0, creditedCycles);
|
|
855
|
+
const prevModes = new Set(creditedModesCycles.map((c) => c.mode));
|
|
856
|
+
if (allModes.has("validation") && !prevModes.has("validation")) companion.stats.patience += 1;
|
|
857
|
+
if (allModes.has("completion") && !prevModes.has("completion")) companion.stats.patience += 1;
|
|
858
|
+
const creditedWisdom = session.companionCreditedWisdom ?? 0;
|
|
859
|
+
const totalWisdom = computeWisdomGain(session);
|
|
860
|
+
companion.stats.wisdom += Math.max(0, totalWisdom - creditedWisdom);
|
|
861
|
+
updateRepoMemory(companion, session.cwd, "completion", deltaActiveMs);
|
|
862
|
+
if (totalCycles <= 3) {
|
|
863
|
+
companion.consecutiveEfficientSessions++;
|
|
864
|
+
} else {
|
|
865
|
+
companion.consecutiveEfficientSessions = 0;
|
|
866
|
+
}
|
|
867
|
+
if (totalCycles >= 8) {
|
|
868
|
+
companion.consecutiveHighCycleSessions++;
|
|
869
|
+
} else {
|
|
870
|
+
companion.consecutiveHighCycleSessions = 0;
|
|
871
|
+
}
|
|
872
|
+
const hasCrash = session.agents.some((a) => a.status === "crashed");
|
|
873
|
+
if (hasCrash) {
|
|
874
|
+
companion.consecutiveCleanSessions = 0;
|
|
875
|
+
companion.sessionsCrashed++;
|
|
876
|
+
} else {
|
|
877
|
+
companion.consecutiveCleanSessions++;
|
|
878
|
+
}
|
|
879
|
+
companion.recentCompletions.push((/* @__PURE__ */ new Date()).toISOString());
|
|
880
|
+
if (companion.recentCompletions.length > 30) {
|
|
881
|
+
companion.recentCompletions = companion.recentCompletions.slice(-30);
|
|
882
|
+
}
|
|
883
|
+
const taskKey = normalizeTask(session.task, session.cwd);
|
|
884
|
+
companion.taskHistory[taskKey] = (companion.taskHistory[taskKey] ?? 0) + 1;
|
|
885
|
+
const baselines = companion.baselines ?? defaultBaselines();
|
|
886
|
+
welfordUpdate(baselines.sessionMs, session.activeMs);
|
|
887
|
+
welfordUpdate(baselines.cycleCount, totalCycles);
|
|
888
|
+
welfordUpdate(baselines.agentCount, session.agents.length);
|
|
889
|
+
welfordUpdate(baselines.recentAgentThroughput, companion.lastRecentAgentCount ?? 0);
|
|
890
|
+
const today = todayIso();
|
|
891
|
+
if (baselines.lastCountedDay === null) {
|
|
892
|
+
baselines.lastCountedDay = today;
|
|
893
|
+
baselines.pendingDayCount = 1;
|
|
894
|
+
} else if (baselines.lastCountedDay === today) {
|
|
895
|
+
baselines.pendingDayCount++;
|
|
896
|
+
} else {
|
|
897
|
+
welfordUpdate(baselines.sessionsPerDay, baselines.pendingDayCount);
|
|
898
|
+
const lastDay = /* @__PURE__ */ new Date(baselines.lastCountedDay + "T12:00:00");
|
|
899
|
+
const yesterdayDate = /* @__PURE__ */ new Date(today + "T12:00:00");
|
|
900
|
+
yesterdayDate.setDate(yesterdayDate.getDate() - 1);
|
|
901
|
+
const cursor = new Date(lastDay);
|
|
902
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
903
|
+
while (cursor < yesterdayDate) {
|
|
904
|
+
welfordUpdate(baselines.sessionsPerDay, 0);
|
|
905
|
+
cursor.setDate(cursor.getDate() + 1);
|
|
906
|
+
}
|
|
907
|
+
baselines.lastCountedDay = today;
|
|
908
|
+
baselines.pendingDayCount = 1;
|
|
909
|
+
}
|
|
910
|
+
companion.baselines = baselines;
|
|
911
|
+
recomputeXpLevelTitle(companion);
|
|
912
|
+
const newAchievementIds = checkAchievements(companion, session);
|
|
913
|
+
if (newAchievementIds.length > 0) {
|
|
914
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
915
|
+
for (const id of newAchievementIds) {
|
|
916
|
+
companion.achievements.push({ id, unlockedAt: now });
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
return newAchievementIds;
|
|
920
|
+
}
|
|
921
|
+
function onAgentSpawned(companion) {
|
|
922
|
+
companion.lifetimeAgentsSpawned++;
|
|
923
|
+
}
|
|
924
|
+
function onAgentCrashed(companion) {
|
|
925
|
+
companion.consecutiveCleanSessions = 0;
|
|
926
|
+
}
|
|
927
|
+
function normalizeTask(task, cwd) {
|
|
928
|
+
const normalized = task.toLowerCase().replace(/\s+/g, " ").trim().slice(0, 100);
|
|
929
|
+
const cwdBase = cwd.split("/").pop() ?? cwd;
|
|
930
|
+
return `${cwdBase}:${normalized}`;
|
|
931
|
+
}
|
|
932
|
+
function captureObservationContext(companion, _repoCwd) {
|
|
933
|
+
return {
|
|
934
|
+
prevLevel: companion.level,
|
|
935
|
+
prevSessionsCompleted: companion.sessionsCompleted,
|
|
936
|
+
prevConsecutiveEfficientSessions: companion.consecutiveEfficientSessions ?? 0
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
async function runPostSessionObservations(companion, session, prev) {
|
|
940
|
+
return runObservationEngine({ companion, session, prev });
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/daemon/companion-memory.ts
|
|
944
|
+
var MAX_OBSERVATIONS = 200;
|
|
945
|
+
var OBSERVATION_TEXT_REJECT_RE = /[<>]/;
|
|
946
|
+
var CONTROL_CHARS_DETECT_RE = /[\x00-\x1f\x7f]/;
|
|
947
|
+
var CONTROL_CHARS_STRIP_RE = /[\x00-\x1f\x7f]/g;
|
|
948
|
+
function isSafeObservationText(text) {
|
|
949
|
+
if (OBSERVATION_TEXT_REJECT_RE.test(text)) return false;
|
|
950
|
+
if (CONTROL_CHARS_DETECT_RE.test(text)) return false;
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
953
|
+
function sanitizeForDisplay(text) {
|
|
954
|
+
return text.replace(CONTROL_CHARS_STRIP_RE, "");
|
|
955
|
+
}
|
|
956
|
+
var memoryPathOverride = null;
|
|
957
|
+
function resolvedMemoryPath() {
|
|
958
|
+
return memoryPathOverride ?? companionMemoryPath();
|
|
959
|
+
}
|
|
960
|
+
var writeQueue = Promise.resolve();
|
|
961
|
+
function enqueueWrite(op) {
|
|
962
|
+
const next = writeQueue.then(() => op());
|
|
963
|
+
writeQueue = next.then(
|
|
964
|
+
() => void 0,
|
|
965
|
+
(_err) => void 0
|
|
966
|
+
);
|
|
967
|
+
return next;
|
|
968
|
+
}
|
|
969
|
+
function defaultMemoryState() {
|
|
970
|
+
return { version: 1, observations: [], prunedAt: null, firedDetectors: {} };
|
|
971
|
+
}
|
|
972
|
+
function isCompanionMemoryState(x) {
|
|
973
|
+
return typeof x === "object" && x !== null && x["version"] === 1 && Array.isArray(x["observations"]);
|
|
974
|
+
}
|
|
975
|
+
function fillDefaults(state) {
|
|
976
|
+
if (state.prunedAt == null) state.prunedAt = null;
|
|
977
|
+
if (state.firedDetectors == null) state.firedDetectors = {};
|
|
978
|
+
return state;
|
|
979
|
+
}
|
|
980
|
+
function loadMemoryStrict() {
|
|
981
|
+
const path = resolvedMemoryPath();
|
|
982
|
+
if (!existsSync2(path)) return defaultMemoryState();
|
|
983
|
+
let raw;
|
|
984
|
+
try {
|
|
985
|
+
raw = readFileSync2(path, "utf-8");
|
|
986
|
+
} catch (err) {
|
|
987
|
+
throw new MemoryStoreParseError(err);
|
|
988
|
+
}
|
|
989
|
+
let parsed;
|
|
990
|
+
try {
|
|
991
|
+
parsed = JSON.parse(raw);
|
|
992
|
+
} catch (err) {
|
|
993
|
+
throw new MemoryStoreParseError(err);
|
|
994
|
+
}
|
|
995
|
+
if (!isCompanionMemoryState(parsed)) {
|
|
996
|
+
throw new MemoryStoreParseError(new Error("shape validation failed"));
|
|
997
|
+
}
|
|
998
|
+
const state = parsed;
|
|
999
|
+
if (state.version !== 1) {
|
|
1000
|
+
throw new MemoryStoreParseError(new Error(`unsupported version: ${state.version}`));
|
|
1001
|
+
}
|
|
1002
|
+
return fillDefaults(state);
|
|
1003
|
+
}
|
|
1004
|
+
function loadMemory() {
|
|
1005
|
+
try {
|
|
1006
|
+
return loadMemoryStrict();
|
|
1007
|
+
} catch (err) {
|
|
1008
|
+
if (err instanceof MemoryStoreParseError) {
|
|
1009
|
+
console.error("[companion-memory]", err.message, "details:", err.cause instanceof Error ? err.cause.message : err.cause);
|
|
1010
|
+
return defaultMemoryState();
|
|
1011
|
+
}
|
|
1012
|
+
throw err;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
function saveMemory(store) {
|
|
1016
|
+
const path = resolvedMemoryPath();
|
|
1017
|
+
const dir = dirname2(path);
|
|
1018
|
+
mkdirSync2(dir, { recursive: true });
|
|
1019
|
+
const tmp = join2(dir, `.companion-memory.${randomUUID2()}.tmp`);
|
|
1020
|
+
writeFileSync2(tmp, JSON.stringify(store, null, 2), "utf-8");
|
|
1021
|
+
renameSync2(tmp, path);
|
|
1022
|
+
}
|
|
1023
|
+
function appendObservations(records, detectorUpdates) {
|
|
1024
|
+
const hasUpdates = detectorUpdates != null && Object.keys(detectorUpdates).length > 0;
|
|
1025
|
+
if (records.length === 0 && !hasUpdates) {
|
|
1026
|
+
return Promise.resolve();
|
|
1027
|
+
}
|
|
1028
|
+
return enqueueWrite(() => {
|
|
1029
|
+
const store = loadMemory();
|
|
1030
|
+
const keptRecords = records.filter((rec) => {
|
|
1031
|
+
if (!rec.detectorId) return true;
|
|
1032
|
+
const currentKey = detectorUpdates?.[rec.detectorId];
|
|
1033
|
+
const lastKey = store.firedDetectors[rec.detectorId];
|
|
1034
|
+
return currentKey !== lastKey;
|
|
1035
|
+
});
|
|
1036
|
+
store.observations.push(...keptRecords);
|
|
1037
|
+
if (detectorUpdates) {
|
|
1038
|
+
for (const [k, v] of Object.entries(detectorUpdates)) store.firedDetectors[k] = v;
|
|
1039
|
+
}
|
|
1040
|
+
if (store.observations.length > MAX_OBSERVATIONS) {
|
|
1041
|
+
store.observations = store.observations.slice(-MAX_OBSERVATIONS);
|
|
1042
|
+
store.prunedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1043
|
+
}
|
|
1044
|
+
saveMemory(store);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
function queryRecent(opts) {
|
|
1048
|
+
const store = loadMemory();
|
|
1049
|
+
let records = store.observations;
|
|
1050
|
+
if (opts.repo !== void 0) {
|
|
1051
|
+
records = records.filter((rec) => rec.repo === opts.repo);
|
|
1052
|
+
}
|
|
1053
|
+
return records.slice().sort((a, b) => b.timestamp.localeCompare(a.timestamp)).slice(0, opts.limit);
|
|
1054
|
+
}
|
|
1055
|
+
var MEMORY_INJECTION_LIMIT = 5;
|
|
1056
|
+
function escapeMemoryText(text) {
|
|
1057
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
1058
|
+
}
|
|
1059
|
+
function buildMemoryContext(repo) {
|
|
1060
|
+
if (!repo) return "";
|
|
1061
|
+
const recent = queryRecent({ repo, limit: MEMORY_INJECTION_LIMIT });
|
|
1062
|
+
if (recent.length === 0) return "";
|
|
1063
|
+
const lines = recent.map((o) => `- ${escapeMemoryText(o.text)}`).join("\n");
|
|
1064
|
+
return "\n## Recent observations\n" + lines + "\n## End observations";
|
|
1065
|
+
}
|
|
1066
|
+
function pickPhrase(phrases, sessionId) {
|
|
1067
|
+
let hash = 0;
|
|
1068
|
+
for (let i = 0; i < sessionId.length; i++) {
|
|
1069
|
+
hash = hash * 31 + sessionId.charCodeAt(i) | 0;
|
|
1070
|
+
}
|
|
1071
|
+
return phrases[Math.abs(hash) % phrases.length];
|
|
1072
|
+
}
|
|
1073
|
+
function checkGrindingSession(input, lastDedupKey) {
|
|
1074
|
+
const { companion, session } = input;
|
|
1075
|
+
const baselines = companion.baselines;
|
|
1076
|
+
if (!baselines || baselines.sessionMs.count < 5) return null;
|
|
1077
|
+
const activeMs = session.activeMs ?? 0;
|
|
1078
|
+
const cycles = session.orchestratorCycles?.length ?? 0;
|
|
1079
|
+
if (!(activeMs >= 1.5 * baselines.sessionMs.mean && cycles >= 8)) return null;
|
|
1080
|
+
const dedupKey = `day:${todayIso()}`;
|
|
1081
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1082
|
+
const phrases = [
|
|
1083
|
+
"That session took twice as long as my average and felt like four times as much work.",
|
|
1084
|
+
"Eight cycles and counting. I have made peace with the boulder having opinions.",
|
|
1085
|
+
"I spent longer on that than I do on most things. The hill had strong feelings today.",
|
|
1086
|
+
"That was a grind. Not a metaphorical one. Actually just a very long push.",
|
|
1087
|
+
"The boulder put in overtime. So did I. Neither of us asked for this."
|
|
1088
|
+
];
|
|
1089
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1090
|
+
}
|
|
1091
|
+
function checkSwiftVictory(input, lastDedupKey) {
|
|
1092
|
+
const { companion, session } = input;
|
|
1093
|
+
const baselines = companion.baselines;
|
|
1094
|
+
if (!baselines || baselines.sessionMs.count < 5) return null;
|
|
1095
|
+
const cycles = session.orchestratorCycles?.length ?? 0;
|
|
1096
|
+
const crashedAgents = session.agents?.filter((a) => a.status === "crashed" || a.status === "lost").length ?? 0;
|
|
1097
|
+
const activeMs = session.activeMs ?? 0;
|
|
1098
|
+
if (!(cycles <= 3 && crashedAgents === 0 && activeMs <= 0.75 * baselines.sessionMs.mean)) return null;
|
|
1099
|
+
const dedupKey = `day:${todayIso()}`;
|
|
1100
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1101
|
+
const phrases = [
|
|
1102
|
+
"Three cycles, no crashes, done before I had time to get anxious. Almost suspicious.",
|
|
1103
|
+
"That one was quick and clean. I do not fully trust it but I will take it.",
|
|
1104
|
+
"Finished well under my average with no casualties. The hill barely put up a fight.",
|
|
1105
|
+
"Fast, clean, done. I keep waiting for the other shoe to drop.",
|
|
1106
|
+
"That session ran like it was embarrassed to take too long."
|
|
1107
|
+
];
|
|
1108
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1109
|
+
}
|
|
1110
|
+
function checkBruisingSession(input, lastDedupKey) {
|
|
1111
|
+
const { session } = input;
|
|
1112
|
+
const crashedAgents = session.agents?.filter((a) => a.status === "crashed" || a.status === "lost").length ?? 0;
|
|
1113
|
+
if (crashedAgents < 3) return null;
|
|
1114
|
+
const dedupKey = `day:${todayIso()}`;
|
|
1115
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1116
|
+
const phrases = [
|
|
1117
|
+
`Three or more agents down. The hill took casualties today and I noticed.`,
|
|
1118
|
+
"More agents crashed than survived that one. I am counting this as a learning experience.",
|
|
1119
|
+
"The attrition rate was uncomfortable. I have had worse, but not recently.",
|
|
1120
|
+
"I lost enough agents that I started naming them in my head. Not ideal.",
|
|
1121
|
+
"Multiple agents did not make it back. The boulder was in a mood."
|
|
1122
|
+
];
|
|
1123
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1124
|
+
}
|
|
1125
|
+
function checkFaithfulRepo(input, lastDedupKey) {
|
|
1126
|
+
const { companion, session } = input;
|
|
1127
|
+
const repo = companion.repos?.[session.cwd];
|
|
1128
|
+
if (!repo) return null;
|
|
1129
|
+
const visits = repo.visits;
|
|
1130
|
+
const MILESTONES = /* @__PURE__ */ new Set([10, 25, 50, 100]);
|
|
1131
|
+
if (!MILESTONES.has(visits)) return null;
|
|
1132
|
+
const dedupKey = `repo:${session.cwd}:visits:${visits}`;
|
|
1133
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1134
|
+
const phrases = [
|
|
1135
|
+
`I have come back to this repo ${visits} times now. It knows me. I know it. We have an understanding.`,
|
|
1136
|
+
`Visit number ${visits} to this codebase. At this point it is practically muscle memory.`,
|
|
1137
|
+
`${visits} sessions in this repo. The boulder has worn a groove in the familiar path.`,
|
|
1138
|
+
`Back here for the ${visits}th time. Some repos just keep calling me back.`
|
|
1139
|
+
];
|
|
1140
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1141
|
+
}
|
|
1142
|
+
function checkTroubledRepo(input, lastDedupKey) {
|
|
1143
|
+
const { companion, session } = input;
|
|
1144
|
+
const repo = companion.repos?.[session.cwd];
|
|
1145
|
+
if (!repo) return null;
|
|
1146
|
+
const { crashes, visits } = repo;
|
|
1147
|
+
if (visits < 5) return null;
|
|
1148
|
+
const MILESTONES = /* @__PURE__ */ new Set([5, 10, 20]);
|
|
1149
|
+
if (!MILESTONES.has(crashes)) return null;
|
|
1150
|
+
if (crashes / visits < 0.4) return null;
|
|
1151
|
+
const dedupKey = `repo:${session.cwd}:crashes:${crashes}`;
|
|
1152
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1153
|
+
const phrases = [
|
|
1154
|
+
`This repo has crashed my agents ${crashes} times now. We have a complicated relationship.`,
|
|
1155
|
+
`${crashes} crashes in this codebase. It has opinions about my approach and they are violent.`,
|
|
1156
|
+
`The crash rate here is notable. I keep coming back. Make of that what you will.`,
|
|
1157
|
+
`${crashes} agent failures in this repo. Some hills are just steeper than others.`
|
|
1158
|
+
];
|
|
1159
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1160
|
+
}
|
|
1161
|
+
function checkProductiveRepo(input, lastDedupKey) {
|
|
1162
|
+
const { companion, session } = input;
|
|
1163
|
+
const repo = companion.repos?.[session.cwd];
|
|
1164
|
+
if (!repo) return null;
|
|
1165
|
+
if (repo.moodAvg === void 0) return null;
|
|
1166
|
+
const MILESTONES = /* @__PURE__ */ new Set([10, 25, 50]);
|
|
1167
|
+
const completions = repo.completions;
|
|
1168
|
+
if (!MILESTONES.has(completions)) return null;
|
|
1169
|
+
if (repo.moodAvg < 0.65) return null;
|
|
1170
|
+
const dedupKey = `repo:${session.cwd}:completions:${completions}`;
|
|
1171
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1172
|
+
const phrases = [
|
|
1173
|
+
`${completions} sessions completed in this repo and the mood trend is good. Rare.`,
|
|
1174
|
+
`This codebase has been unusually cooperative. ${completions} completions and counting.`,
|
|
1175
|
+
`${completions} sessions, solid mood average. This repo treats me well for once.`,
|
|
1176
|
+
`Reached ${completions} completions here with a decent track record. I trust this hill.`
|
|
1177
|
+
];
|
|
1178
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1179
|
+
}
|
|
1180
|
+
function checkSisypheanRepeat(input, lastDedupKey) {
|
|
1181
|
+
const { companion, session } = input;
|
|
1182
|
+
const taskKey = normalizeTask(session.task ?? "", session.cwd);
|
|
1183
|
+
const count = companion.taskHistory?.[taskKey] ?? 0;
|
|
1184
|
+
const MILESTONES = /* @__PURE__ */ new Set([3, 5, 10]);
|
|
1185
|
+
if (!MILESTONES.has(count)) return null;
|
|
1186
|
+
const dedupKey = `task:${taskKey}:${count}`;
|
|
1187
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1188
|
+
const phrases = [
|
|
1189
|
+
`I have done this task ${count} times. The boulder remembers. So do I.`,
|
|
1190
|
+
`Back at this one for the ${count}th time. The definition of insanity is famously doing the same thing.`,
|
|
1191
|
+
`${count} attempts at this task. I am nothing if not persistent.`,
|
|
1192
|
+
`This is my ${count}th run at this particular boulder. It has not gotten lighter.`
|
|
1193
|
+
];
|
|
1194
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1195
|
+
}
|
|
1196
|
+
function checkDayStreak(input, lastDedupKey) {
|
|
1197
|
+
const { companion } = input;
|
|
1198
|
+
const MILESTONES = /* @__PURE__ */ new Set([7, 14, 30, 60]);
|
|
1199
|
+
const days = companion.consecutiveDaysActive ?? 0;
|
|
1200
|
+
if (!MILESTONES.has(days)) return null;
|
|
1201
|
+
const dedupKey = `value:${days}`;
|
|
1202
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1203
|
+
const phrases = [
|
|
1204
|
+
`${days} days in a row now. The boulder does not take weekends.`,
|
|
1205
|
+
`A ${days}-day streak. I have been here every single day. The hill appreciates the consistency, probably.`,
|
|
1206
|
+
`${days} consecutive days active. At this point it is less a habit and more a fact of my existence.`,
|
|
1207
|
+
`Day ${days} without a break. The boulder is starting to feel like an old friend.`
|
|
1208
|
+
];
|
|
1209
|
+
return { text: pickPhrase(phrases, input.session.id), dedupKey };
|
|
1210
|
+
}
|
|
1211
|
+
function checkEfficientStreak(input, lastDedupKey) {
|
|
1212
|
+
const { companion, prev } = input;
|
|
1213
|
+
const MILESTONES = /* @__PURE__ */ new Set([5, 10, 20]);
|
|
1214
|
+
const streak = companion.consecutiveEfficientSessions ?? 0;
|
|
1215
|
+
if (!MILESTONES.has(streak)) return null;
|
|
1216
|
+
if (streak <= prev.prevConsecutiveEfficientSessions) return null;
|
|
1217
|
+
const dedupKey = `value:${streak}`;
|
|
1218
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1219
|
+
const phrases = [
|
|
1220
|
+
`${streak} efficient sessions in a row. The boulder has been cooperative. I do not know why.`,
|
|
1221
|
+
`An ${streak}-session efficient streak. I am running well and choosing not to question it.`,
|
|
1222
|
+
`${streak} consecutive clean-and-fast sessions. Peak form, or regression to the mean incoming.`,
|
|
1223
|
+
`${streak} efficient sessions back to back. The hill feels different when things actually work.`
|
|
1224
|
+
];
|
|
1225
|
+
return { text: pickPhrase(phrases, input.session.id), dedupKey };
|
|
1226
|
+
}
|
|
1227
|
+
function checkLevelUp(input, lastDedupKey) {
|
|
1228
|
+
const { companion, prev } = input;
|
|
1229
|
+
if (companion.level <= prev.prevLevel) return null;
|
|
1230
|
+
const dedupKey = `level:${companion.level}`;
|
|
1231
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1232
|
+
const phrases = [
|
|
1233
|
+
`I reached level ${companion.level}. The title is new. The boulder is the same.`,
|
|
1234
|
+
`Level ${companion.level} now. ${companion.title}. The promotion comes with no raise but considerable irony.`,
|
|
1235
|
+
`Leveled up to ${companion.level}. Whatever title that brings, I have earned it the hardest possible way.`,
|
|
1236
|
+
`Level ${companion.level}: ${companion.title}. The gods have acknowledged my persistence. Minimally.`
|
|
1237
|
+
];
|
|
1238
|
+
return { text: pickPhrase(phrases, input.session.id), dedupKey };
|
|
1239
|
+
}
|
|
1240
|
+
function checkSessionMilestone(input, lastDedupKey) {
|
|
1241
|
+
const { companion } = input;
|
|
1242
|
+
const MILESTONES = /* @__PURE__ */ new Set([10, 50, 100, 250, 500, 1e3]);
|
|
1243
|
+
const completed = companion.sessionsCompleted ?? 0;
|
|
1244
|
+
if (!MILESTONES.has(completed)) return null;
|
|
1245
|
+
const dedupKey = `count:${completed}`;
|
|
1246
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1247
|
+
const phrases = [
|
|
1248
|
+
`${completed} sessions completed. The boulder has been up the hill that many times. I counted.`,
|
|
1249
|
+
`Session number ${completed}. I have stopped trying to imagine an end to this.`,
|
|
1250
|
+
`${completed} total sessions. The number stopped feeling large around half that mark.`,
|
|
1251
|
+
`I have completed ${completed} sessions now. The hill is the same. I am slightly different.`
|
|
1252
|
+
];
|
|
1253
|
+
return { text: pickPhrase(phrases, input.session.id), dedupKey };
|
|
1254
|
+
}
|
|
1255
|
+
function checkLargeSwarm(input, lastDedupKey) {
|
|
1256
|
+
const { companion, session } = input;
|
|
1257
|
+
const agentCount = session.agents?.length ?? 0;
|
|
1258
|
+
const baselines = companion.baselines;
|
|
1259
|
+
const meetsAbsolute = agentCount >= 10;
|
|
1260
|
+
const meetsRelative = baselines && baselines.agentCount.count >= 5 && agentCount >= 2 * baselines.agentCount.mean;
|
|
1261
|
+
if (!meetsAbsolute && !meetsRelative) return null;
|
|
1262
|
+
const dedupKey = `day:${todayIso()}`;
|
|
1263
|
+
if (dedupKey === lastDedupKey) return null;
|
|
1264
|
+
const phrases = [
|
|
1265
|
+
`I had ${agentCount} agents running at once. The boulder had help today. Lots of help.`,
|
|
1266
|
+
`${agentCount} agents. A proper swarm. The hill did not know what hit it.`,
|
|
1267
|
+
`Ran ${agentCount} agents in parallel. This is either impressive or something I will explain to someone later.`,
|
|
1268
|
+
`${agentCount} agents this session. The boulder has never been pushed by so many at once.`
|
|
1269
|
+
];
|
|
1270
|
+
return { text: pickPhrase(phrases, session.id), dedupKey };
|
|
1271
|
+
}
|
|
1272
|
+
var RULE_DETECTORS = [
|
|
1273
|
+
{ id: "grinding-session", category: "session-sentiments", check: checkGrindingSession },
|
|
1274
|
+
{ id: "swift-victory", category: "session-sentiments", check: checkSwiftVictory },
|
|
1275
|
+
{ id: "bruising-session", category: "session-sentiments", check: checkBruisingSession },
|
|
1276
|
+
{ id: "faithful-repo", category: "repo-impressions", check: checkFaithfulRepo },
|
|
1277
|
+
{ id: "troubled-repo", category: "repo-impressions", check: checkTroubledRepo },
|
|
1278
|
+
{ id: "productive-repo", category: "repo-impressions", check: checkProductiveRepo },
|
|
1279
|
+
{ id: "sisyphean-repeat", category: "user-patterns", check: checkSisypheanRepeat },
|
|
1280
|
+
{ id: "day-streak", category: "user-patterns", check: checkDayStreak },
|
|
1281
|
+
{ id: "efficient-streak", category: "user-patterns", check: checkEfficientStreak },
|
|
1282
|
+
{ id: "level-up", category: "notable-moments", check: checkLevelUp },
|
|
1283
|
+
{ id: "session-milestone", category: "notable-moments", check: checkSessionMilestone },
|
|
1284
|
+
{ id: "large-swarm", category: "notable-moments", check: checkLargeSwarm }
|
|
1285
|
+
];
|
|
1286
|
+
function runRuleDetectors(input, firedDetectors) {
|
|
1287
|
+
const records = [];
|
|
1288
|
+
const detectorUpdates = {};
|
|
1289
|
+
for (const detector of RULE_DETECTORS) {
|
|
1290
|
+
try {
|
|
1291
|
+
const lastDedupKey = firedDetectors[detector.id] ?? null;
|
|
1292
|
+
const result = detector.check(input, lastDedupKey);
|
|
1293
|
+
if (result !== null) {
|
|
1294
|
+
records.push({
|
|
1295
|
+
id: randomUUID2(),
|
|
1296
|
+
category: detector.category,
|
|
1297
|
+
source: "rule",
|
|
1298
|
+
text: result.text,
|
|
1299
|
+
repo: input.session.cwd,
|
|
1300
|
+
sessionId: input.session.id,
|
|
1301
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1302
|
+
detectorId: detector.id
|
|
1303
|
+
});
|
|
1304
|
+
detectorUpdates[detector.id] = result.dedupKey;
|
|
1305
|
+
}
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
console.error("[companion-memory] detector failed", {
|
|
1308
|
+
detectorId: detector.id,
|
|
1309
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1310
|
+
errorName: err instanceof Error ? err.name : "UnknownError"
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
return { records, detectorUpdates };
|
|
1315
|
+
}
|
|
1316
|
+
var OBSERVATION_JSON_SCHEMA = {
|
|
1317
|
+
type: "object",
|
|
1318
|
+
properties: {
|
|
1319
|
+
category: {
|
|
1320
|
+
type: "string",
|
|
1321
|
+
enum: [...OBSERVATION_CATEGORIES],
|
|
1322
|
+
description: "Which of the four observation categories best fits this observation"
|
|
1323
|
+
},
|
|
1324
|
+
text: {
|
|
1325
|
+
type: "string",
|
|
1326
|
+
minLength: 10,
|
|
1327
|
+
maxLength: 180,
|
|
1328
|
+
description: "One sentence, first-person, no angle brackets or control characters"
|
|
1329
|
+
}
|
|
1330
|
+
},
|
|
1331
|
+
required: ["category", "text"],
|
|
1332
|
+
additionalProperties: false
|
|
1333
|
+
};
|
|
1334
|
+
var ObservationZodSchema = z.object({
|
|
1335
|
+
category: z.enum(OBSERVATION_CATEGORIES),
|
|
1336
|
+
text: z.string().min(10).max(180).refine(isSafeObservationText, "contains unsafe characters")
|
|
1337
|
+
});
|
|
1338
|
+
async function defaultCallHaikuStructured(prompt) {
|
|
1339
|
+
return callHaikuStructured(prompt, OBSERVATION_JSON_SCHEMA, ObservationZodSchema);
|
|
1340
|
+
}
|
|
1341
|
+
async function runHaikuObservation(input, caller) {
|
|
1342
|
+
try {
|
|
1343
|
+
const { companion, session } = input;
|
|
1344
|
+
const callHaiku2 = caller ?? defaultCallHaikuStructured;
|
|
1345
|
+
const prompt = `<role>You observe the developer at the end of each session and write one short qualitative note.</role>
|
|
1346
|
+
<voice>One sentence. First-person impression. Wry, self-deprecating, absurd. No meta-system language.
|
|
1347
|
+
Do not use angle brackets (< or >) or quotation marks. Plain text only.</voice>
|
|
1348
|
+
<state>
|
|
1349
|
+
Level: ${companion.level} (${companion.title})
|
|
1350
|
+
Session cycles: ${session.orchestratorCycles?.length ?? 0}
|
|
1351
|
+
Session activeMs: ${session.activeMs ?? 0}
|
|
1352
|
+
Crashed agents: ${session.agents?.filter((a) => a.status === "crashed" || a.status === "lost").length ?? 0}
|
|
1353
|
+
Streaks: clean=${companion.consecutiveCleanSessions ?? 0}, efficient=${companion.consecutiveEfficientSessions ?? 0}, days-active=${companion.consecutiveDaysActive ?? 0}
|
|
1354
|
+
</state>
|
|
1355
|
+
Pick the most relevant category and write one observation about this session.`;
|
|
1356
|
+
const result = await callHaiku2(prompt);
|
|
1357
|
+
if (!result) return null;
|
|
1358
|
+
if (!isSafeObservationText(result.text)) {
|
|
1359
|
+
console.error("[companion-memory] haiku observation dropped \u2014 unsafe text", {
|
|
1360
|
+
source: "haiku",
|
|
1361
|
+
reason: "unsafe-text",
|
|
1362
|
+
textLength: result.text.length
|
|
1363
|
+
});
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
return {
|
|
1367
|
+
id: randomUUID2(),
|
|
1368
|
+
category: result.category,
|
|
1369
|
+
source: "haiku",
|
|
1370
|
+
text: result.text,
|
|
1371
|
+
repo: session.cwd,
|
|
1372
|
+
sessionId: session.id,
|
|
1373
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1374
|
+
};
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
console.error("[companion-memory] haiku observation failed", {
|
|
1377
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1378
|
+
errorName: err instanceof Error ? err.name : "UnknownError"
|
|
1379
|
+
});
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
async function runObservationEngine(input, opts) {
|
|
1384
|
+
try {
|
|
1385
|
+
const current = loadMemory();
|
|
1386
|
+
const ruleResult = runRuleDetectors(input, current.firedDetectors);
|
|
1387
|
+
let haikuRecord = null;
|
|
1388
|
+
try {
|
|
1389
|
+
haikuRecord = await (opts?.haikuCaller ?? ((i) => runHaikuObservation(i)))(input);
|
|
1390
|
+
} catch (haikuErr) {
|
|
1391
|
+
console.error("[companion-memory] haiku caller threw in engine", {
|
|
1392
|
+
errorMessage: haikuErr instanceof Error ? haikuErr.message : String(haikuErr),
|
|
1393
|
+
errorName: haikuErr instanceof Error ? haikuErr.name : "UnknownError"
|
|
1394
|
+
});
|
|
1395
|
+
}
|
|
1396
|
+
const allRecords = haikuRecord ? [...ruleResult.records, haikuRecord] : ruleResult.records;
|
|
1397
|
+
if (allRecords.length > 0 || Object.keys(ruleResult.detectorUpdates).length > 0) {
|
|
1398
|
+
await appendObservations(allRecords, ruleResult.detectorUpdates);
|
|
1399
|
+
}
|
|
1400
|
+
} catch (err) {
|
|
1401
|
+
console.error("[companion-memory] observation engine error", {
|
|
1402
|
+
errorMessage: err instanceof Error ? err.message : String(err),
|
|
1403
|
+
errorName: err instanceof Error ? err.name : "UnknownError"
|
|
1404
|
+
});
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// src/shared/companion-render.ts
|
|
1409
|
+
import stringWidth from "string-width";
|
|
1410
|
+
function sliceToWidth(s, maxCols) {
|
|
1411
|
+
let w = 0;
|
|
1412
|
+
let i = 0;
|
|
1413
|
+
while (i < s.length) {
|
|
1414
|
+
const cp = s.codePointAt(i);
|
|
1415
|
+
const ch = String.fromCodePoint(cp);
|
|
1416
|
+
const cw = stringWidth(ch);
|
|
1417
|
+
if (w + cw > maxCols) break;
|
|
1418
|
+
w += cw;
|
|
1419
|
+
i += ch.length;
|
|
1420
|
+
}
|
|
1421
|
+
return s.slice(0, i);
|
|
1422
|
+
}
|
|
1423
|
+
var IDLE_HOBBIES = [
|
|
1424
|
+
"reading Camus",
|
|
1425
|
+
"stacking pebbles",
|
|
1426
|
+
"watching clouds",
|
|
1427
|
+
"sketching boulders",
|
|
1428
|
+
"counting stars",
|
|
1429
|
+
"writing haiku",
|
|
1430
|
+
"practicing zen",
|
|
1431
|
+
"studying geology",
|
|
1432
|
+
"polishing rocks",
|
|
1433
|
+
"mapping the hill",
|
|
1434
|
+
"resting",
|
|
1435
|
+
"stargazing",
|
|
1436
|
+
"whittling",
|
|
1437
|
+
"collecting fossils",
|
|
1438
|
+
"napping on summit",
|
|
1439
|
+
"journaling",
|
|
1440
|
+
"stretching",
|
|
1441
|
+
"humming",
|
|
1442
|
+
"doodling",
|
|
1443
|
+
"tending moss",
|
|
1444
|
+
"making tea",
|
|
1445
|
+
"reading Myth of Sisyphus",
|
|
1446
|
+
"reorganizing rocks",
|
|
1447
|
+
"people watching",
|
|
1448
|
+
"whistling"
|
|
1449
|
+
];
|
|
1450
|
+
var SPINNER_VERBS = [
|
|
1451
|
+
// physical
|
|
1452
|
+
"pushing",
|
|
1453
|
+
"hauling",
|
|
1454
|
+
"heaving",
|
|
1455
|
+
"toiling",
|
|
1456
|
+
"straining",
|
|
1457
|
+
"trudging",
|
|
1458
|
+
"laboring",
|
|
1459
|
+
"rolling",
|
|
1460
|
+
"ascending",
|
|
1461
|
+
"dragging",
|
|
1462
|
+
"shouldering",
|
|
1463
|
+
"hoisting",
|
|
1464
|
+
"lugging",
|
|
1465
|
+
"schlepping",
|
|
1466
|
+
"grinding",
|
|
1467
|
+
"lifting",
|
|
1468
|
+
"bracing",
|
|
1469
|
+
"climbing",
|
|
1470
|
+
"leaning in",
|
|
1471
|
+
"digging in",
|
|
1472
|
+
// philosophical
|
|
1473
|
+
"philosophizing",
|
|
1474
|
+
"contemplating",
|
|
1475
|
+
"pondering",
|
|
1476
|
+
"musing",
|
|
1477
|
+
"ruminating",
|
|
1478
|
+
"reflecting",
|
|
1479
|
+
"meditating",
|
|
1480
|
+
"wondering",
|
|
1481
|
+
"questioning",
|
|
1482
|
+
"theorizing",
|
|
1483
|
+
"considering",
|
|
1484
|
+
"deliberating",
|
|
1485
|
+
"introspecting",
|
|
1486
|
+
"cogitating",
|
|
1487
|
+
"brooding",
|
|
1488
|
+
// endurance
|
|
1489
|
+
"persevering",
|
|
1490
|
+
"enduring",
|
|
1491
|
+
"persisting",
|
|
1492
|
+
"sustaining",
|
|
1493
|
+
"weathering",
|
|
1494
|
+
"carrying on",
|
|
1495
|
+
"pressing on",
|
|
1496
|
+
"holding steady",
|
|
1497
|
+
"keeping at it",
|
|
1498
|
+
"not stopping",
|
|
1499
|
+
// light/silly
|
|
1500
|
+
"napping",
|
|
1501
|
+
"procrastinating",
|
|
1502
|
+
"daydreaming",
|
|
1503
|
+
"vibing",
|
|
1504
|
+
"winging it",
|
|
1505
|
+
"hoping",
|
|
1506
|
+
"improvising",
|
|
1507
|
+
"making do",
|
|
1508
|
+
"whistling"
|
|
1509
|
+
];
|
|
1510
|
+
function getBaseForm(level) {
|
|
1511
|
+
if (level <= 2) return "(FACE) {BOULDER}";
|
|
1512
|
+
if (level <= 4) return "(FACE)/ {BOULDER}";
|
|
1513
|
+
if (level <= 7) return "/(FACE)/ {BOULDER}";
|
|
1514
|
+
if (level <= 11) return "\\(FACE)/ {BOULDER}";
|
|
1515
|
+
if (level <= 19) return "\u1566(FACE)\u1564 {BOULDER}";
|
|
1516
|
+
return "\u265B\u1566(FACE)\u1564 {BOULDER}";
|
|
1517
|
+
}
|
|
1518
|
+
var MOOD_FACES = {
|
|
1519
|
+
happy: ["^.^", "^\u203F^", "\u2727\u203F\u2727"],
|
|
1520
|
+
grinding: [">.<", ">_<", "\xF2.\xF3"],
|
|
1521
|
+
frustrated: [">.<#", "\u0CA0_\u0CA0", "\u0CA0\u76CA\u0CA0"],
|
|
1522
|
+
zen: ["\u203E.\u203E", "\u203E\u203F\u203E", "\u02D8\u203F\u02D8"],
|
|
1523
|
+
sleepy: ["-.-)zzZ", "-_-)zzZ", "\u02D8.\u02D8)zzZ"],
|
|
1524
|
+
excited: ["*o*", "*\u25E1*", "\u2726\u25E1\u2726"],
|
|
1525
|
+
existential: ["\u25C9_\u25C9", "\u2299_\u2299", "\u25C9\u2038\u25C9"]
|
|
1526
|
+
};
|
|
1527
|
+
function getMoodFace(mood, intensity = 0) {
|
|
1528
|
+
const faces = MOOD_FACES[mood];
|
|
1529
|
+
if (!faces) throw new Error(`Unknown mood: ${mood}`);
|
|
1530
|
+
const tier = intensity < 30 ? 0 : intensity <= 70 ? 1 : 2;
|
|
1531
|
+
return faces[tier];
|
|
1532
|
+
}
|
|
1533
|
+
function getStatCosmetics(stats) {
|
|
1534
|
+
const cosmetics = [];
|
|
1535
|
+
if (stats.wisdom > 5) cosmetics.push("wisps");
|
|
1536
|
+
if (stats.endurance > 36e6) cosmetics.push("trail");
|
|
1537
|
+
if (stats.patience > 50) cosmetics.push("zen-prefix");
|
|
1538
|
+
return cosmetics;
|
|
1539
|
+
}
|
|
1540
|
+
function getBoulderForm(agentCount, repoNickname) {
|
|
1541
|
+
let boulder;
|
|
1542
|
+
if (agentCount === void 0 || agentCount <= 0) {
|
|
1543
|
+
boulder = "";
|
|
1544
|
+
} else if (agentCount <= 2) {
|
|
1545
|
+
boulder = "o";
|
|
1546
|
+
} else if (agentCount <= 6) {
|
|
1547
|
+
boulder = "O";
|
|
1548
|
+
} else if (agentCount <= 15) {
|
|
1549
|
+
boulder = "\u25C9";
|
|
1550
|
+
} else if (agentCount <= 35) {
|
|
1551
|
+
boulder = "@";
|
|
1552
|
+
} else {
|
|
1553
|
+
boulder = "@@";
|
|
1554
|
+
}
|
|
1555
|
+
if (repoNickname !== void 0) {
|
|
1556
|
+
boulder = `${boulder} "${repoNickname}"`;
|
|
1557
|
+
}
|
|
1558
|
+
return boulder;
|
|
1559
|
+
}
|
|
1560
|
+
function composeLine(body, cosmetics, boulder) {
|
|
1561
|
+
let b = boulder;
|
|
1562
|
+
let hasZenPrefix = false;
|
|
1563
|
+
if (boulder !== "") {
|
|
1564
|
+
for (const c of cosmetics) {
|
|
1565
|
+
switch (c) {
|
|
1566
|
+
case "wisps":
|
|
1567
|
+
b = `~${b}~`;
|
|
1568
|
+
break;
|
|
1569
|
+
case "trail":
|
|
1570
|
+
b = `${b} ...`;
|
|
1571
|
+
break;
|
|
1572
|
+
case "zen-prefix":
|
|
1573
|
+
hasZenPrefix = true;
|
|
1574
|
+
break;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
} else {
|
|
1578
|
+
if (cosmetics.includes("zen-prefix")) hasZenPrefix = true;
|
|
1579
|
+
}
|
|
1580
|
+
let line = b === "" ? body.replace(" {BOULDER}", "") : body.replace("{BOULDER}", b);
|
|
1581
|
+
if (hasZenPrefix) line = `\u262F ${line}`;
|
|
1582
|
+
return line;
|
|
1583
|
+
}
|
|
1584
|
+
var MOOD_COLORS = {
|
|
1585
|
+
happy: { ansi: 32, tmux: "green" },
|
|
1586
|
+
grinding: { ansi: 33, tmux: "yellow" },
|
|
1587
|
+
frustrated: { ansi: 31, tmux: "red" },
|
|
1588
|
+
zen: { ansi: 36, tmux: "cyan" },
|
|
1589
|
+
sleepy: { ansi: 90, tmux: "colour245" },
|
|
1590
|
+
excited: { ansi: 97, tmux: "white" },
|
|
1591
|
+
existential: { ansi: 35, tmux: "magenta" }
|
|
1592
|
+
};
|
|
1593
|
+
function getMoodTmuxColor(mood) {
|
|
1594
|
+
return MOOD_COLORS[mood].tmux;
|
|
1595
|
+
}
|
|
1596
|
+
function getMoodAnsiCode(mood) {
|
|
1597
|
+
return MOOD_COLORS[mood].ansi;
|
|
1598
|
+
}
|
|
1599
|
+
function colorize(text, mood, tmux) {
|
|
1600
|
+
const { ansi, tmux: tmuxColor } = MOOD_COLORS[mood];
|
|
1601
|
+
if (tmux) {
|
|
1602
|
+
return `#[fg=${tmuxColor}]${text}#[fg=default]`;
|
|
1603
|
+
}
|
|
1604
|
+
return `\x1B[${ansi}m${text}\x1B[0m`;
|
|
1605
|
+
}
|
|
1606
|
+
function statSummary(stats) {
|
|
1607
|
+
const endH = Math.floor(stats.endurance / 36e5);
|
|
1608
|
+
return `STR:${stats.strength} END:${endH}h WIS:${stats.wisdom} PAT:${stats.patience}`;
|
|
1609
|
+
}
|
|
1610
|
+
function renderCompanion(companion, fields, opts) {
|
|
1611
|
+
const hasFace = fields.includes("face");
|
|
1612
|
+
const hasBoulder = fields.includes("boulder");
|
|
1613
|
+
const repoNickname = opts?.repoPath !== void 0 ? companion.repos[opts.repoPath]?.nickname ?? void 0 : void 0;
|
|
1614
|
+
const boulder = getBoulderForm(opts?.agentCount, repoNickname);
|
|
1615
|
+
const cosmetics = getStatCosmetics(companion.stats);
|
|
1616
|
+
let facePart = null;
|
|
1617
|
+
let boulderOnlyPart = null;
|
|
1618
|
+
if (hasFace) {
|
|
1619
|
+
const baseForm = getBaseForm(companion.level);
|
|
1620
|
+
const intensity = companion.debugMood?.scores[companion.mood] ?? 0;
|
|
1621
|
+
const face = getMoodFace(companion.mood, intensity);
|
|
1622
|
+
const bodyWithFace = baseForm.replace("FACE", face);
|
|
1623
|
+
facePart = composeLine(bodyWithFace, cosmetics, boulder);
|
|
1624
|
+
} else if (hasBoulder) {
|
|
1625
|
+
boulderOnlyPart = boulder;
|
|
1626
|
+
}
|
|
1627
|
+
let commentary = fields.includes("commentary") ? companion.lastCommentary?.text ?? "" : null;
|
|
1628
|
+
const parts = [];
|
|
1629
|
+
for (const field of fields) {
|
|
1630
|
+
switch (field) {
|
|
1631
|
+
case "face":
|
|
1632
|
+
if (facePart !== null) parts.push(facePart);
|
|
1633
|
+
break;
|
|
1634
|
+
case "boulder":
|
|
1635
|
+
if (!hasFace && boulderOnlyPart !== null) parts.push(boulderOnlyPart);
|
|
1636
|
+
break;
|
|
1637
|
+
case "title":
|
|
1638
|
+
parts.push(companion.title);
|
|
1639
|
+
break;
|
|
1640
|
+
case "commentary":
|
|
1641
|
+
if (commentary !== null) parts.push(commentary);
|
|
1642
|
+
break;
|
|
1643
|
+
case "mood":
|
|
1644
|
+
parts.push(`[${companion.mood}]`);
|
|
1645
|
+
break;
|
|
1646
|
+
case "level":
|
|
1647
|
+
parts.push(`Lv ${companion.level}`);
|
|
1648
|
+
break;
|
|
1649
|
+
case "stats":
|
|
1650
|
+
parts.push(statSummary(companion.stats));
|
|
1651
|
+
break;
|
|
1652
|
+
case "achievements":
|
|
1653
|
+
parts.push(`${companion.achievements.length} achievements`);
|
|
1654
|
+
break;
|
|
1655
|
+
case "verb": {
|
|
1656
|
+
const idx = (opts?.verbIndex ?? companion.spinnerVerbIndex) % SPINNER_VERBS.length;
|
|
1657
|
+
parts.push(SPINNER_VERBS[idx]);
|
|
1658
|
+
break;
|
|
1659
|
+
}
|
|
1660
|
+
case "hobby": {
|
|
1661
|
+
const hobbyIdx = ((/* @__PURE__ */ new Date()).getHours() + companion.level) % IDLE_HOBBIES.length;
|
|
1662
|
+
parts.push(IDLE_HOBBIES[hobbyIdx]);
|
|
1663
|
+
break;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (opts?.maxWidth !== void 0) {
|
|
1668
|
+
const maxWidth = opts.maxWidth;
|
|
1669
|
+
const joined = parts.join(" ");
|
|
1670
|
+
const joinedWidth = stringWidth(joined);
|
|
1671
|
+
if (joinedWidth > maxWidth && commentary !== null && commentary.length > 0) {
|
|
1672
|
+
const commentaryIdx = parts.indexOf(commentary);
|
|
1673
|
+
if (commentaryIdx !== -1) {
|
|
1674
|
+
const commentaryWidth = stringWidth(commentary);
|
|
1675
|
+
const overhead = joinedWidth - commentaryWidth;
|
|
1676
|
+
const available = maxWidth - overhead - 2;
|
|
1677
|
+
if (available < 0) {
|
|
1678
|
+
parts[commentaryIdx] = "";
|
|
1679
|
+
} else {
|
|
1680
|
+
parts[commentaryIdx] = sliceToWidth(commentary, available);
|
|
1681
|
+
}
|
|
1682
|
+
commentary = parts[commentaryIdx];
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
const result2 = parts.filter((p) => p.length > 0).join(" ");
|
|
1686
|
+
const resultWidth = stringWidth(result2);
|
|
1687
|
+
const final = resultWidth > maxWidth ? sliceToWidth(result2, maxWidth - 1) + "\u2026" : result2;
|
|
1688
|
+
return applyColor(final, fields, facePart, companion.mood, opts);
|
|
1689
|
+
}
|
|
1690
|
+
const result = parts.filter((p) => p.length > 0).join(" ");
|
|
1691
|
+
return applyColor(result, fields, facePart, companion.mood, opts);
|
|
1692
|
+
}
|
|
1693
|
+
function applyColor(result, fields, facePart, mood, opts) {
|
|
1694
|
+
const useColor = opts?.color === true || opts?.tmuxFormat === true;
|
|
1695
|
+
if (!useColor || facePart === null || !fields.includes("face")) return result;
|
|
1696
|
+
const tmux = opts?.tmuxFormat === true;
|
|
1697
|
+
const coloredFace = colorize(facePart, mood, tmux);
|
|
1698
|
+
return result.replace(facePart, coloredFace);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
// src/shared/session-export.ts
|
|
1702
|
+
import { execFile } from "child_process";
|
|
1703
|
+
import { promisify } from "util";
|
|
1704
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3, mkdirSync as mkdirSync3, symlinkSync, rmSync, writeFileSync as writeFileSync3 } from "fs";
|
|
1705
|
+
import { homedir } from "os";
|
|
1706
|
+
import { join as join3 } from "path";
|
|
1707
|
+
function sanitizeName(name) {
|
|
1708
|
+
return name.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
|
|
1709
|
+
}
|
|
1710
|
+
function buildOutputPath(label, dir) {
|
|
1711
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1712
|
+
mkdirSync3(dir, { recursive: true });
|
|
1713
|
+
const base = `sisyphus-${label}-${date}`;
|
|
1714
|
+
let candidate = join3(dir, `${base}.zip`);
|
|
1715
|
+
let counter = 1;
|
|
1716
|
+
while (existsSync3(candidate)) {
|
|
1717
|
+
counter++;
|
|
1718
|
+
candidate = join3(dir, `${base}-${counter}.zip`);
|
|
1719
|
+
}
|
|
1720
|
+
return candidate;
|
|
1721
|
+
}
|
|
1722
|
+
function generateGuide() {
|
|
1723
|
+
return `# Sisyphus Session Export
|
|
1724
|
+
|
|
1725
|
+
## Quick Orientation
|
|
1726
|
+
|
|
1727
|
+
Start with \`session/state.json\` for the full session state, or \`history/session.json\` for a compact summary with metrics.
|
|
1728
|
+
|
|
1729
|
+
## session/
|
|
1730
|
+
|
|
1731
|
+
Project-local session data \u2014 the orchestrator's working directory.
|
|
1732
|
+
|
|
1733
|
+
### Top-level files
|
|
1734
|
+
- **state.json** \u2014 Complete session state: id, task, status, timing, and the full \`agents[]\` array (each agent has id, type, instruction, status, reports, Claude session ID, and resume args)
|
|
1735
|
+
- **goal.md** \u2014 The task description; updated if the goal evolves across phases
|
|
1736
|
+
- **initial-prompt.md** \u2014 Verbatim user input that started the session
|
|
1737
|
+
- **roadmap.md** \u2014 Orchestrator's working memory: current stage, exit criteria, active context files, next steps
|
|
1738
|
+
- **strategy.md** \u2014 Work breakdown: completed stages, current stage decomposition (concerns/phases), and what's ahead
|
|
1739
|
+
- **digest.json** \u2014 4-field snapshot: \`recentWork\`, \`unusualEvents\`, \`currentActivity\`, \`whatsNext\`
|
|
1740
|
+
|
|
1741
|
+
### Subdirectories
|
|
1742
|
+
|
|
1743
|
+
**context/** \u2014 Research artifacts produced by agents and consumed by downstream agents
|
|
1744
|
+
- \`explore-*.md\` \u2014 Codebase exploration findings (key files, architecture notes)
|
|
1745
|
+
- \`requirements*.md/json\` \u2014 Feature requirements (structured + human-readable)
|
|
1746
|
+
- \`design*.md/json\` \u2014 Architecture specs, decision records, diagrams
|
|
1747
|
+
- \`{agent-id}/plan*.md\` \u2014 Implementation plans (tasks, files to touch, dependencies) \u2014 per plan-lead subdirectory
|
|
1748
|
+
- \`e2e-recipe.md\` \u2014 End-to-end validation steps
|
|
1749
|
+
- \`review-*.md\` \u2014 Code review findings (severity-ranked)
|
|
1750
|
+
- \`completion-summary.md\` \u2014 Final handoff document
|
|
1751
|
+
|
|
1752
|
+
**logs/** \u2014 One \`cycle-NNN.md\` per orchestrator cycle. Each logs what happened, agents spawned, user decisions, and key findings.
|
|
1753
|
+
|
|
1754
|
+
**prompts/** \u2014 Full agent configs, one set per agent:
|
|
1755
|
+
- \`agent-NNN-system.md\` \u2014 System prompt (instructions, tools, output format)
|
|
1756
|
+
- \`agent-NNN-run.sh\` \u2014 Executable bash script to resume the agent (contains env, CLI args, instruction)
|
|
1757
|
+
- \`agent-NNN-plugin/\` \u2014 Plugin directory (hooks, sub-agent configs)
|
|
1758
|
+
|
|
1759
|
+
**reports/** \u2014 Agent deliverables:
|
|
1760
|
+
- \`agent-NNN-final.md\` \u2014 Final report (findings, implementation summary, or review results)
|
|
1761
|
+
- \`agent-NNN-00N.md\` \u2014 Interim progress reports (optional)
|
|
1762
|
+
|
|
1763
|
+
**snapshots/** \u2014 Point-in-time checkpoints (\`snapshots/cycle-N/\`). Each contains state.json, roadmap.md, strategy.md, and logs/ as they were at that cycle boundary. Used for rollback.
|
|
1764
|
+
|
|
1765
|
+
**.tui/** \u2014 Lightweight TUI render cache (cycle summaries for display). Regenerable; not primary data.
|
|
1766
|
+
|
|
1767
|
+
## history/
|
|
1768
|
+
|
|
1769
|
+
Global telemetry from the daemon \u2014 timing, events, and aggregate metrics.
|
|
1770
|
+
|
|
1771
|
+
- **events.jsonl** \u2014 Newline-delimited JSON event stream. Each line: \`{ ts, event, sessionId, data }\`. Events include session-start, agent-spawned, agent-completed, cycle-boundary, signals-snapshot, session-end, etc. Complete audit trail.
|
|
1772
|
+
- **session.json** \u2014 Summary: id, name, task, status, timing (activeMs, wallClockMs, efficiency), agent/cycle counts, crash/rollback counts, completion report, and a compact agents array.
|
|
1773
|
+
`;
|
|
1774
|
+
}
|
|
1775
|
+
var execFileAsync = promisify(execFile);
|
|
1776
|
+
async function exportSessionToZip(sessionId, cwd, options) {
|
|
1777
|
+
const reveal = options?.reveal ?? true;
|
|
1778
|
+
const sessDir = sessionDir(cwd, sessionId);
|
|
1779
|
+
const histDir = historySessionDir(sessionId);
|
|
1780
|
+
const sessExists = existsSync3(sessDir);
|
|
1781
|
+
const histExists = existsSync3(histDir);
|
|
1782
|
+
if (!sessExists && !histExists) {
|
|
1783
|
+
throw new Error(`No data found for session ${sessionId}`);
|
|
1784
|
+
}
|
|
1785
|
+
let label = sessionId.slice(0, 8);
|
|
1786
|
+
const stPath = statePath(cwd, sessionId);
|
|
1787
|
+
if (existsSync3(stPath)) {
|
|
1788
|
+
try {
|
|
1789
|
+
const state = JSON.parse(readFileSync3(stPath, "utf-8"));
|
|
1790
|
+
if (state.name) {
|
|
1791
|
+
label = sanitizeName(state.name);
|
|
1792
|
+
}
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
const dir = options?.outputDir ?? join3(homedir(), "Downloads");
|
|
1797
|
+
const outputPath = buildOutputPath(label, dir);
|
|
1798
|
+
const tmpDir = `/tmp/sisyphus-export-${sessionId.slice(0, 8)}-${Date.now()}`;
|
|
1799
|
+
try {
|
|
1800
|
+
mkdirSync3(tmpDir, { recursive: true });
|
|
1801
|
+
writeFileSync3(join3(tmpDir, "CLAUDE.md"), generateGuide(), "utf-8");
|
|
1802
|
+
if (sessExists) {
|
|
1803
|
+
symlinkSync(sessDir, join3(tmpDir, "session"));
|
|
1804
|
+
}
|
|
1805
|
+
if (histExists) {
|
|
1806
|
+
symlinkSync(histDir, join3(tmpDir, "history"));
|
|
1807
|
+
}
|
|
1808
|
+
const parts = ["CLAUDE.md", sessExists ? "session/" : "", histExists ? "history/" : ""].filter(Boolean);
|
|
1809
|
+
await execFileAsync("zip", ["-rq", outputPath, ...parts], { cwd: tmpDir });
|
|
1810
|
+
} finally {
|
|
1811
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
1812
|
+
}
|
|
1813
|
+
if (reveal) {
|
|
1814
|
+
try {
|
|
1815
|
+
await execFileAsync("open", ["-R", outputPath]);
|
|
1816
|
+
} catch {
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
return outputPath;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/shared/format.ts
|
|
1823
|
+
function formatDuration(startOrMs, endIso) {
|
|
1824
|
+
let totalMs;
|
|
1825
|
+
if (typeof startOrMs === "number") {
|
|
1826
|
+
totalMs = startOrMs;
|
|
1827
|
+
} else {
|
|
1828
|
+
const start = new Date(startOrMs).getTime();
|
|
1829
|
+
const end = endIso ? new Date(endIso).getTime() : Date.now();
|
|
1830
|
+
totalMs = end - start;
|
|
1831
|
+
}
|
|
1832
|
+
const totalSeconds = Math.floor(totalMs / 1e3);
|
|
1833
|
+
if (totalSeconds < 0) return "0s";
|
|
1834
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
1835
|
+
const minutes = Math.floor(totalSeconds % 3600 / 60);
|
|
1836
|
+
const seconds = totalSeconds % 60;
|
|
1837
|
+
if (hours > 0) return `${hours}h${minutes}m`;
|
|
1838
|
+
if (minutes > 0) return `${minutes}m${seconds}s`;
|
|
1839
|
+
return `${seconds}s`;
|
|
1840
|
+
}
|
|
1841
|
+
function statusColor(status) {
|
|
1842
|
+
switch (status) {
|
|
1843
|
+
case "active":
|
|
1844
|
+
case "running":
|
|
1845
|
+
return "green";
|
|
1846
|
+
case "completed":
|
|
1847
|
+
return "cyan";
|
|
1848
|
+
case "paused":
|
|
1849
|
+
return "yellow";
|
|
1850
|
+
case "killed":
|
|
1851
|
+
case "crashed":
|
|
1852
|
+
return "red";
|
|
1853
|
+
case "lost":
|
|
1854
|
+
return "gray";
|
|
1855
|
+
default:
|
|
1856
|
+
return "white";
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
export {
|
|
1861
|
+
augmentedPath,
|
|
1862
|
+
execEnv,
|
|
1863
|
+
EXEC_ENV,
|
|
1864
|
+
exec,
|
|
1865
|
+
execSafe,
|
|
1866
|
+
callHaiku,
|
|
1867
|
+
callHaikuWithTools,
|
|
1868
|
+
callHaikuStructured,
|
|
1869
|
+
MemoryStoreParseError,
|
|
1870
|
+
ACHIEVEMENTS,
|
|
1871
|
+
sanitizeForDisplay,
|
|
1872
|
+
loadMemoryStrict,
|
|
1873
|
+
buildMemoryContext,
|
|
1874
|
+
loadCompanion,
|
|
1875
|
+
saveCompanion,
|
|
1876
|
+
recordCommentary,
|
|
1877
|
+
recordFeedback,
|
|
1878
|
+
computeStrengthGain,
|
|
1879
|
+
computeLevelProgress,
|
|
1880
|
+
getTitle,
|
|
1881
|
+
computeMood,
|
|
1882
|
+
onSessionStart,
|
|
1883
|
+
computeWisdomGain,
|
|
1884
|
+
onSessionComplete,
|
|
1885
|
+
onAgentSpawned,
|
|
1886
|
+
onAgentCrashed,
|
|
1887
|
+
captureObservationContext,
|
|
1888
|
+
runPostSessionObservations,
|
|
1889
|
+
SPINNER_VERBS,
|
|
1890
|
+
getMoodFace,
|
|
1891
|
+
getMoodTmuxColor,
|
|
1892
|
+
getMoodAnsiCode,
|
|
1893
|
+
renderCompanion,
|
|
1894
|
+
exportSessionToZip,
|
|
1895
|
+
formatDuration,
|
|
1896
|
+
statusColor
|
|
1897
|
+
};
|
|
1898
|
+
//# sourceMappingURL=chunk-36VJ7ZBD.js.map
|