aoaoe 1.0.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -1
- package/dist/cli-completions.d.ts +24 -0
- package/dist/cli-completions.js +114 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +8 -1
- package/dist/index.js +67 -1
- package/dist/input.d.ts +9 -0
- package/dist/input.js +30 -0
- package/dist/service-generator.d.ts +32 -0
- package/dist/service-generator.js +132 -0
- package/dist/session-replay.d.ts +25 -0
- package/dist/session-replay.js +103 -0
- package/dist/workflow-engine.d.ts +48 -0
- package/dist/workflow-engine.js +91 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<a href="https://github.com/Talador12/agent-of-agent-of-empires/actions/workflows/ci.yml"><img src="https://github.com/Talador12/agent-of-agent-of-empires/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
5
5
|
<a href="https://www.npmjs.com/package/aoaoe"><img src="https://img.shields.io/npm/v/aoaoe" alt="npm version"></a>
|
|
6
6
|
<a href="https://github.com/Talador12/agent-of-agent-of-empires/releases"><img src="https://img.shields.io/github/v/release/Talador12/agent-of-agent-of-empires" alt="GitHub release"></a>
|
|
7
|
-
<img src="https://img.shields.io/badge/tests-
|
|
7
|
+
<img src="https://img.shields.io/badge/tests-3332-brightgreen" alt="tests">
|
|
8
8
|
<img src="https://img.shields.io/badge/node-%3E%3D20-blue" alt="Node.js >= 20">
|
|
9
9
|
<img src="https://img.shields.io/badge/runtime%20deps-0-brightgreen" alt="zero runtime dependencies">
|
|
10
10
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
|
|
@@ -822,6 +822,28 @@ The daemon and chat UI communicate via files in `~/.aoaoe/`:
|
|
|
822
822
|
| `chat.pid` | chat UI | daemon | Chat process PID for detection |
|
|
823
823
|
| `actions.log` | daemon | -- | Persistent action history (JSONL) |
|
|
824
824
|
|
|
825
|
+
## Intelligence Modules
|
|
826
|
+
|
|
827
|
+
aoaoe includes 55 intelligence modules that run every daemon tick without LLM calls. They handle observability, automation, and fleet management autonomously:
|
|
828
|
+
|
|
829
|
+
**Reasoning Pipeline** (7 gates, runs before every LLM call):
|
|
830
|
+
- Fleet rate limiter, observation cache, priority filter, context compressor, LLM call, approval workflow, cost tracker
|
|
831
|
+
|
|
832
|
+
**Per-Tick Autonomous Systems**:
|
|
833
|
+
- Session summarizer, conflict detector + auto-resolver, goal completion detector, cost budget enforcer
|
|
834
|
+
- Activity heatmap, budget predictor, task retry with backoff, adaptive poll controller
|
|
835
|
+
- Fleet SLA monitor, progress velocity tracker, recovery playbook, dependency scheduler
|
|
836
|
+
- Session graduation (trust ladder), fleet utilization tracker, anomaly detector
|
|
837
|
+
- Workflow engine (fan-out/fan-in stage orchestration)
|
|
838
|
+
|
|
839
|
+
**On-Demand Analytics** (via TUI commands):
|
|
840
|
+
- Audit trail + search, fleet snapshots + diff, lifecycle analytics, cost attribution
|
|
841
|
+
- Goal decomposition, difficulty scoring, smart nudge generation, template auto-detection
|
|
842
|
+
- Fleet-wide search, nudge effectiveness tracking, difficulty-based allocation
|
|
843
|
+
- Goal refinement from completed task patterns, fleet HTML export, session replay
|
|
844
|
+
|
|
845
|
+
See `AGENTS.md` for the full source layout with all 55 modules described.
|
|
846
|
+
|
|
825
847
|
## Project Structure
|
|
826
848
|
|
|
827
849
|
```
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const CLI_COMMANDS: readonly ["init", "tasks", "progress", "health", "summary", "supervisor", "incident", "runbook", "adopt", "doctor", "stats", "replay", "export", "tail", "web", "sync", "test-context", "task", "config"];
|
|
2
|
+
export declare const CLI_FLAGS: readonly ["--config", "--verbose", "--dry-run", "--observe", "--confirm", "--json", "--ndjson", "--watch", "--changes-only", "--heartbeat", "--follow", "--help", "--version"];
|
|
3
|
+
export declare const TUI_COMMANDS: readonly ["/supervisor", "/incident", "/runbook", "/progress", "/health", "/prompt-template", "/pin-save", "/pin-load", "/pin-presets", "/activity", "/conflicts", "/heatmap", "/audit", "/audit-stats", "/audit-search", "/fleet-snap", "/budget-predict", "/retries", "/fleet-forecast", "/priority", "/escalations", "/poll-status", "/drift", "/goal-progress", "/pool", "/reasoner-cost", "/anomaly", "/sla", "/velocity", "/schedule", "/cost-summary", "/session-report", "/cache", "/rate-limit", "/recovery", "/lifecycle", "/cost-report", "/decompose", "/memory", "/dep-graph", "/approvals", "/approve", "/reject", "/fleet-diff", "/template", "/difficulty", "/smart-nudge", "/utilization", "/detect-template", "/fleet-search", "/nudge-stats", "/allocation", "/graduation", "/refine", "/export", "/clear"];
|
|
4
|
+
/**
|
|
5
|
+
* Generate bash completion script.
|
|
6
|
+
*/
|
|
7
|
+
export declare function generateBashCompletion(): string;
|
|
8
|
+
/**
|
|
9
|
+
* Generate zsh completion script.
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateZshCompletion(): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate fish completion script.
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateFishCompletion(): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate completion script for the specified shell.
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateCompletion(shell: "bash" | "zsh" | "fish"): string;
|
|
20
|
+
/**
|
|
21
|
+
* List all available commands for help display.
|
|
22
|
+
*/
|
|
23
|
+
export declare function formatCommandList(): string[];
|
|
24
|
+
//# sourceMappingURL=cli-completions.d.ts.map
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// cli-completions.ts — shell autocomplete for all aoaoe commands and TUI slash commands.
|
|
2
|
+
// generates completion scripts for bash, zsh, and fish.
|
|
3
|
+
export const CLI_COMMANDS = [
|
|
4
|
+
"init", "tasks", "progress", "health", "summary", "supervisor",
|
|
5
|
+
"incident", "runbook", "adopt", "doctor", "stats", "replay",
|
|
6
|
+
"export", "tail", "web", "sync", "test-context",
|
|
7
|
+
"task", "config",
|
|
8
|
+
];
|
|
9
|
+
export const CLI_FLAGS = [
|
|
10
|
+
"--config", "--verbose", "--dry-run", "--observe", "--confirm",
|
|
11
|
+
"--json", "--ndjson", "--watch", "--changes-only", "--heartbeat", "--follow",
|
|
12
|
+
"--help", "--version",
|
|
13
|
+
];
|
|
14
|
+
export const TUI_COMMANDS = [
|
|
15
|
+
"/supervisor", "/incident", "/runbook", "/progress", "/health",
|
|
16
|
+
"/prompt-template", "/pin-save", "/pin-load", "/pin-presets",
|
|
17
|
+
"/activity", "/conflicts", "/heatmap", "/audit", "/audit-stats",
|
|
18
|
+
"/audit-search", "/fleet-snap", "/budget-predict", "/retries",
|
|
19
|
+
"/fleet-forecast", "/priority", "/escalations", "/poll-status",
|
|
20
|
+
"/drift", "/goal-progress", "/pool", "/reasoner-cost",
|
|
21
|
+
"/anomaly", "/sla", "/velocity", "/schedule", "/cost-summary",
|
|
22
|
+
"/session-report", "/cache", "/rate-limit", "/recovery",
|
|
23
|
+
"/lifecycle", "/cost-report", "/decompose", "/memory",
|
|
24
|
+
"/dep-graph", "/approvals", "/approve", "/reject", "/fleet-diff",
|
|
25
|
+
"/template", "/difficulty", "/smart-nudge", "/utilization",
|
|
26
|
+
"/detect-template", "/fleet-search", "/nudge-stats", "/allocation",
|
|
27
|
+
"/graduation", "/refine", "/export", "/clear",
|
|
28
|
+
];
|
|
29
|
+
/**
|
|
30
|
+
* Generate bash completion script.
|
|
31
|
+
*/
|
|
32
|
+
export function generateBashCompletion() {
|
|
33
|
+
const cmds = CLI_COMMANDS.join(" ");
|
|
34
|
+
const flags = CLI_FLAGS.join(" ");
|
|
35
|
+
return `# aoaoe bash completion
|
|
36
|
+
# Add to ~/.bashrc: eval "$(aoaoe completions bash)"
|
|
37
|
+
_aoaoe() {
|
|
38
|
+
local cur prev
|
|
39
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
40
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
41
|
+
|
|
42
|
+
if [[ "\${cur}" == --* ]]; then
|
|
43
|
+
COMPREPLY=($(compgen -W "${flags}" -- "\${cur}"))
|
|
44
|
+
elif [[ "\${COMP_CWORD}" -eq 1 ]]; then
|
|
45
|
+
COMPREPLY=($(compgen -W "${cmds}" -- "\${cur}"))
|
|
46
|
+
fi
|
|
47
|
+
}
|
|
48
|
+
complete -F _aoaoe aoaoe
|
|
49
|
+
`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate zsh completion script.
|
|
53
|
+
*/
|
|
54
|
+
export function generateZshCompletion() {
|
|
55
|
+
const cmdList = CLI_COMMANDS.map((c) => `'${c}'`).join(" ");
|
|
56
|
+
const flagList = CLI_FLAGS.map((f) => `'${f}'`).join(" ");
|
|
57
|
+
return `# aoaoe zsh completion
|
|
58
|
+
# Add to ~/.zshrc: eval "$(aoaoe completions zsh)"
|
|
59
|
+
_aoaoe() {
|
|
60
|
+
local -a commands flags
|
|
61
|
+
commands=(${cmdList})
|
|
62
|
+
flags=(${flagList})
|
|
63
|
+
|
|
64
|
+
if (( CURRENT == 2 )); then
|
|
65
|
+
_describe 'command' commands
|
|
66
|
+
elif [[ "\${words[CURRENT]}" == --* ]]; then
|
|
67
|
+
_describe 'flag' flags
|
|
68
|
+
fi
|
|
69
|
+
}
|
|
70
|
+
compdef _aoaoe aoaoe
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Generate fish completion script.
|
|
75
|
+
*/
|
|
76
|
+
export function generateFishCompletion() {
|
|
77
|
+
const lines = [
|
|
78
|
+
"# aoaoe fish completion",
|
|
79
|
+
"# Add to ~/.config/fish/completions/aoaoe.fish",
|
|
80
|
+
];
|
|
81
|
+
for (const cmd of CLI_COMMANDS) {
|
|
82
|
+
lines.push(`complete -c aoaoe -n '__fish_use_subcommand' -a '${cmd}'`);
|
|
83
|
+
}
|
|
84
|
+
for (const flag of CLI_FLAGS) {
|
|
85
|
+
lines.push(`complete -c aoaoe -l '${flag.replace(/^--/, "")}'`);
|
|
86
|
+
}
|
|
87
|
+
return lines.join("\n") + "\n";
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Generate completion script for the specified shell.
|
|
91
|
+
*/
|
|
92
|
+
export function generateCompletion(shell) {
|
|
93
|
+
switch (shell) {
|
|
94
|
+
case "bash": return generateBashCompletion();
|
|
95
|
+
case "zsh": return generateZshCompletion();
|
|
96
|
+
case "fish": return generateFishCompletion();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* List all available commands for help display.
|
|
101
|
+
*/
|
|
102
|
+
export function formatCommandList() {
|
|
103
|
+
const lines = [];
|
|
104
|
+
lines.push(` CLI commands (${CLI_COMMANDS.length}):`);
|
|
105
|
+
lines.push(` ${CLI_COMMANDS.join(", ")}`);
|
|
106
|
+
lines.push("");
|
|
107
|
+
lines.push(` TUI slash commands (${TUI_COMMANDS.length}):`);
|
|
108
|
+
// group in rows of 6
|
|
109
|
+
for (let i = 0; i < TUI_COMMANDS.length; i += 6) {
|
|
110
|
+
lines.push(` ${TUI_COMMANDS.slice(i, i + 6).join(" ")}`);
|
|
111
|
+
}
|
|
112
|
+
return lines;
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=cli-completions.js.map
|
package/dist/config.d.ts
CHANGED
|
@@ -91,6 +91,9 @@ export declare function parseCliArgs(argv: string[]): {
|
|
|
91
91
|
replaySpeed?: number;
|
|
92
92
|
replayLast?: string;
|
|
93
93
|
registerTitle?: string;
|
|
94
|
+
runService: boolean;
|
|
95
|
+
runCompletions: boolean;
|
|
96
|
+
completionsShell?: string;
|
|
94
97
|
};
|
|
95
98
|
export declare function printHelp(): void;
|
|
96
99
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/config.js
CHANGED
|
@@ -359,7 +359,7 @@ export function parseCliArgs(argv) {
|
|
|
359
359
|
let initForce = false;
|
|
360
360
|
let runTaskCli = false;
|
|
361
361
|
let registerTitle;
|
|
362
|
-
const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined };
|
|
362
|
+
const defaults = { overrides, help: false, version: false, register: false, testContext: false, runTest: false, showTasks: false, showTasksJson: false, runProgress: false, progressSince: undefined, progressJson: false, runHealth: false, healthJson: false, runSummary: false, runAdopt: false, adoptTemplate: undefined, showHistory: false, showStatus: false, runRunbook: false, runbookJson: false, runbookSection: undefined, runIncident: false, incidentSince: undefined, incidentLimit: undefined, incidentJson: false, incidentNdjson: false, incidentWatch: false, incidentChangesOnly: false, incidentHeartbeatSec: undefined, incidentIntervalMs: undefined, runSupervisor: false, supervisorAll: false, supervisorSince: undefined, supervisorLimit: undefined, supervisorJson: false, supervisorNdjson: false, supervisorWatch: false, supervisorChangesOnly: false, supervisorHeartbeatSec: undefined, supervisorIntervalMs: undefined, showConfig: false, configValidate: false, configDiff: false, notifyTest: false, runDoctor: false, runBackup: false, backupOutput: undefined, runRestore: false, restoreInput: undefined, runSync: false, syncAction: undefined, syncRemote: undefined, runWeb: false, webPort: undefined, runLogs: false, logsActions: false, logsGrep: undefined, logsCount: undefined, runExport: false, exportFormat: undefined, exportOutput: undefined, exportLast: undefined, exportTasks: false, runInit: false, initForce: false, runTaskCli: false, runTail: false, tailFollow: false, tailCount: undefined, runStats: false, statsLast: undefined, runReplay: false, replaySpeed: undefined, replayLast: undefined, runService: false, runCompletions: false, completionsShell: undefined };
|
|
363
363
|
// check for subcommand as first non-flag arg
|
|
364
364
|
if (argv[2] === "test-context") {
|
|
365
365
|
return { ...defaults, testContext: true };
|
|
@@ -573,6 +573,13 @@ export function parseCliArgs(argv) {
|
|
|
573
573
|
if (argv[2] === "doctor") {
|
|
574
574
|
return { ...defaults, runDoctor: true };
|
|
575
575
|
}
|
|
576
|
+
if (argv[2] === "service") {
|
|
577
|
+
return { ...defaults, runService: true };
|
|
578
|
+
}
|
|
579
|
+
if (argv[2] === "completions") {
|
|
580
|
+
const shell = argv[3] ?? "bash";
|
|
581
|
+
return { ...defaults, runCompletions: true, completionsShell: shell };
|
|
582
|
+
}
|
|
576
583
|
if (argv[2] === "backup") {
|
|
577
584
|
const output = argv[3] && !argv[3].startsWith("-") ? argv[3] : undefined;
|
|
578
585
|
return { ...defaults, runBackup: true, backupOutput: output };
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,9 @@ import { GraduationManager } from "./session-graduation.js";
|
|
|
59
59
|
import { filterThroughApproval, formatApprovalWorkflowStatus } from "./approval-workflow.js";
|
|
60
60
|
import { analyzeCompletedTasks, refineGoal, formatGoalRefinement } from "./goal-refiner.js";
|
|
61
61
|
import { generateHtmlReport, buildReportData } from "./fleet-export.js";
|
|
62
|
+
import { installService } from "./service-generator.js";
|
|
63
|
+
import { buildSessionReplay, formatReplay, summarizeReplay } from "./session-replay.js";
|
|
64
|
+
import { advanceWorkflow, formatWorkflow } from "./workflow-engine.js";
|
|
62
65
|
import { buildLifecycleRecords, computeLifecycleStats, formatLifecycleStats } from "./lifecycle-analytics.js";
|
|
63
66
|
import { buildCostAttributions, computeCostReport, formatCostReport } from "./cost-attribution.js";
|
|
64
67
|
import { decomposeGoal, formatDecomposition } from "./goal-decomposer.js";
|
|
@@ -87,7 +90,7 @@ const AOAOE_DIR = join(homedir(), ".aoaoe"); // watch dir for wakeable sleep
|
|
|
87
90
|
const INPUT_FILE = join(AOAOE_DIR, "pending-input.txt"); // file IPC from chat.ts
|
|
88
91
|
const TASK_RECONCILE_EVERY_POLLS = 6;
|
|
89
92
|
async function main() {
|
|
90
|
-
const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runWeb, webPort, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle } = parseCliArgs(process.argv);
|
|
93
|
+
const { overrides, help, version, register, testContext: isTestContext, runTest, showTasks, showTasksJson, runProgress, progressSince, progressJson, runHealth, healthJson, runSummary, runAdopt, adoptTemplate, showHistory, showStatus, runRunbook, runbookJson, runbookSection, runIncident, incidentSince, incidentLimit, incidentJson, incidentNdjson, incidentWatch, incidentChangesOnly, incidentHeartbeatSec, incidentIntervalMs, runSupervisor, supervisorAll, supervisorSince, supervisorLimit, supervisorJson, supervisorNdjson, supervisorWatch, supervisorChangesOnly, supervisorHeartbeatSec, supervisorIntervalMs, showConfig, configValidate, configDiff, notifyTest, runDoctor, runBackup, backupOutput, runRestore, restoreInput, runSync, syncAction, syncRemote, runWeb, webPort, runLogs, logsActions, logsGrep, logsCount, runExport, exportFormat, exportOutput, exportLast, runInit, initForce, runTaskCli: isTaskCli, runTail: isTail, tailFollow, tailCount, runStats: isStats, statsLast, runReplay: isReplay, replaySpeed, replayLast, registerTitle, runService, runCompletions, completionsShell } = parseCliArgs(process.argv);
|
|
91
94
|
if (help) {
|
|
92
95
|
printHelp();
|
|
93
96
|
process.exit(0);
|
|
@@ -232,6 +235,18 @@ async function main() {
|
|
|
232
235
|
await runDoctorCheck();
|
|
233
236
|
return;
|
|
234
237
|
}
|
|
238
|
+
if (runService) {
|
|
239
|
+
const lines = installService({ workingDir: process.cwd() });
|
|
240
|
+
for (const l of lines)
|
|
241
|
+
console.log(l);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
if (runCompletions) {
|
|
245
|
+
const { generateCompletion } = await import("./cli-completions.js");
|
|
246
|
+
const shell = (completionsShell ?? "bash");
|
|
247
|
+
console.log(generateCompletion(shell));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
235
250
|
if (runBackup) {
|
|
236
251
|
try {
|
|
237
252
|
const result = await createBackup(backupOutput);
|
|
@@ -551,6 +566,7 @@ async function main() {
|
|
|
551
566
|
const recoveryPlaybookManager = new RecoveryPlaybookManager();
|
|
552
567
|
const approvalQueue = new ApprovalQueue();
|
|
553
568
|
const graduationManager = new GraduationManager();
|
|
569
|
+
let activeWorkflow = null;
|
|
554
570
|
// audit: log daemon start
|
|
555
571
|
audit("daemon_start", `daemon started (v${pkg ?? "dev"}, reasoner=${config.reasoner})`);
|
|
556
572
|
const refreshTaskSupervisorState = (reason) => {
|
|
@@ -2420,6 +2436,37 @@ async function main() {
|
|
|
2420
2436
|
writeFileSync(filepath, html);
|
|
2421
2437
|
tui.log("system", `fleet report exported: ${filepath}`);
|
|
2422
2438
|
});
|
|
2439
|
+
// wire /service — generate systemd/launchd service file
|
|
2440
|
+
input.onService(() => {
|
|
2441
|
+
const lines = installService({ workingDir: process.cwd() });
|
|
2442
|
+
for (const l of lines)
|
|
2443
|
+
tui.log("system", l);
|
|
2444
|
+
});
|
|
2445
|
+
// wire /session-replay — session activity timeline replay
|
|
2446
|
+
input.onSessionReplay((target) => {
|
|
2447
|
+
const replay = buildSessionReplay(target);
|
|
2448
|
+
if (replay.events.length === 0) {
|
|
2449
|
+
tui.log("system", `replay: no events for "${target}" — run the daemon to generate audit data first`);
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
const summary = summarizeReplay(replay);
|
|
2453
|
+
for (const l of summary)
|
|
2454
|
+
tui.log("system", l);
|
|
2455
|
+
tui.log("system", "");
|
|
2456
|
+
const detailed = formatReplay(replay);
|
|
2457
|
+
for (const l of detailed)
|
|
2458
|
+
tui.log("system", l);
|
|
2459
|
+
});
|
|
2460
|
+
// wire /workflow — show active workflow state
|
|
2461
|
+
input.onWorkflow(() => {
|
|
2462
|
+
if (!activeWorkflow) {
|
|
2463
|
+
tui.log("system", "workflow: no active workflow (define one in aoaoe.tasks.json with workflow stages)");
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
const lines = formatWorkflow(activeWorkflow);
|
|
2467
|
+
for (const l of lines)
|
|
2468
|
+
tui.log("system", l);
|
|
2469
|
+
});
|
|
2423
2470
|
input.onCostSummary(() => {
|
|
2424
2471
|
const sessions = tui.getSessions();
|
|
2425
2472
|
const summary = computeCostSummary(sessions, tui.getAllSessionCosts());
|
|
@@ -4045,6 +4092,25 @@ async function main() {
|
|
|
4045
4092
|
}
|
|
4046
4093
|
}
|
|
4047
4094
|
}
|
|
4095
|
+
// workflow engine: advance active workflow based on task states
|
|
4096
|
+
if (activeWorkflow && taskManager && tui) {
|
|
4097
|
+
const wf = activeWorkflow; // capture before potential null assignment
|
|
4098
|
+
const taskStates = new Map(taskManager.tasks.map((t) => [t.sessionTitle, t.status]));
|
|
4099
|
+
const { actions: wfActions, completed } = advanceWorkflow(wf, taskStates);
|
|
4100
|
+
for (const a of wfActions) {
|
|
4101
|
+
if (a.type === "activate_task") {
|
|
4102
|
+
const task = taskManager.getTaskForSession(a.detail);
|
|
4103
|
+
if (task && task.status === "pending")
|
|
4104
|
+
task.status = "active";
|
|
4105
|
+
}
|
|
4106
|
+
tui.log("status", `workflow: ${a.type} — ${a.detail}`);
|
|
4107
|
+
audit("task_created", `workflow ${a.type}: ${a.detail}`, a.detail);
|
|
4108
|
+
}
|
|
4109
|
+
if (completed) {
|
|
4110
|
+
tui.log("+ action", `workflow "${wf.name}" completed`);
|
|
4111
|
+
activeWorkflow = null;
|
|
4112
|
+
}
|
|
4113
|
+
}
|
|
4048
4114
|
// trust ladder: record stable tick or failure, sync mode if escalated
|
|
4049
4115
|
if (tui && decisionsThisTick > 0) {
|
|
4050
4116
|
if (actionsFail > 0) {
|
package/dist/input.d.ts
CHANGED
|
@@ -131,6 +131,9 @@ export type AllocationHandler = () => void;
|
|
|
131
131
|
export type GraduationHandler = () => void;
|
|
132
132
|
export type RefineHandler = (target: string) => void;
|
|
133
133
|
export type ExportHandler = () => void;
|
|
134
|
+
export type ServiceHandler = () => void;
|
|
135
|
+
export type SessionReplayHandler = (target: string) => void;
|
|
136
|
+
export type WorkflowHandler = () => void;
|
|
134
137
|
export interface MouseEvent {
|
|
135
138
|
button: number;
|
|
136
139
|
col: number;
|
|
@@ -443,6 +446,12 @@ export declare class InputReader {
|
|
|
443
446
|
onGraduation(handler: GraduationHandler): void;
|
|
444
447
|
onRefine(handler: RefineHandler): void;
|
|
445
448
|
onExport(handler: ExportHandler): void;
|
|
449
|
+
private serviceHandler;
|
|
450
|
+
private sessionReplayHandler;
|
|
451
|
+
private workflowHandler;
|
|
452
|
+
onService(handler: ServiceHandler): void;
|
|
453
|
+
onSessionReplay(handler: SessionReplayHandler): void;
|
|
454
|
+
onWorkflow(handler: WorkflowHandler): void;
|
|
446
455
|
onFleetSearch(handler: FleetSearchHandler): void;
|
|
447
456
|
onNudgeStats(handler: NudgeStatsHandler): void;
|
|
448
457
|
onAllocation(handler: AllocationHandler): void;
|
package/dist/input.js
CHANGED
|
@@ -540,6 +540,12 @@ export class InputReader {
|
|
|
540
540
|
onGraduation(handler) { this.graduationHandler = handler; }
|
|
541
541
|
onRefine(handler) { this.refineHandler = handler; }
|
|
542
542
|
onExport(handler) { this.exportHandler = handler; }
|
|
543
|
+
serviceHandler = null;
|
|
544
|
+
sessionReplayHandler = null;
|
|
545
|
+
workflowHandler = null;
|
|
546
|
+
onService(handler) { this.serviceHandler = handler; }
|
|
547
|
+
onSessionReplay(handler) { this.sessionReplayHandler = handler; }
|
|
548
|
+
onWorkflow(handler) { this.workflowHandler = handler; }
|
|
543
549
|
onFleetSearch(handler) { this.fleetSearchHandler = handler; }
|
|
544
550
|
onNudgeStats(handler) { this.nudgeStatsHandler = handler; }
|
|
545
551
|
onAllocation(handler) { this.allocationHandler = handler; }
|
|
@@ -2364,6 +2370,30 @@ ${BOLD}other:${RESET}
|
|
|
2364
2370
|
else
|
|
2365
2371
|
console.error(`${DIM}export not available (no TUI)${RESET}`);
|
|
2366
2372
|
break;
|
|
2373
|
+
case "/service":
|
|
2374
|
+
if (this.serviceHandler)
|
|
2375
|
+
this.serviceHandler();
|
|
2376
|
+
else
|
|
2377
|
+
console.error(`${DIM}service not available (no TUI)${RESET}`);
|
|
2378
|
+
break;
|
|
2379
|
+
case "/session-replay": {
|
|
2380
|
+
const rpArg = line.slice("/session-replay".length).trim();
|
|
2381
|
+
if (!rpArg) {
|
|
2382
|
+
console.error(`${DIM}usage: /session-replay <session-name>${RESET}`);
|
|
2383
|
+
break;
|
|
2384
|
+
}
|
|
2385
|
+
if (this.sessionReplayHandler)
|
|
2386
|
+
this.sessionReplayHandler(rpArg);
|
|
2387
|
+
else
|
|
2388
|
+
console.error(`${DIM}session-replay not available (no TUI)${RESET}`);
|
|
2389
|
+
break;
|
|
2390
|
+
}
|
|
2391
|
+
case "/workflow":
|
|
2392
|
+
if (this.workflowHandler)
|
|
2393
|
+
this.workflowHandler();
|
|
2394
|
+
else
|
|
2395
|
+
console.error(`${DIM}workflow not available (no TUI)${RESET}`);
|
|
2396
|
+
break;
|
|
2367
2397
|
case "/clear":
|
|
2368
2398
|
process.stderr.write("\x1b[2J\x1b[H");
|
|
2369
2399
|
break;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface ServiceConfig {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
execPath: string;
|
|
5
|
+
workingDir: string;
|
|
6
|
+
configPath?: string;
|
|
7
|
+
user?: string;
|
|
8
|
+
restartSec: number;
|
|
9
|
+
logPath?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate a systemd unit file for Linux.
|
|
13
|
+
*/
|
|
14
|
+
export declare function generateSystemdUnit(config?: Partial<ServiceConfig>): string;
|
|
15
|
+
/**
|
|
16
|
+
* Generate a launchd plist file for macOS.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateLaunchdPlist(config?: Partial<ServiceConfig>): string;
|
|
19
|
+
/**
|
|
20
|
+
* Detect the current platform and generate the appropriate service file.
|
|
21
|
+
*/
|
|
22
|
+
export declare function generateServiceFile(config?: Partial<ServiceConfig>): {
|
|
23
|
+
content: string;
|
|
24
|
+
filename: string;
|
|
25
|
+
installPath: string;
|
|
26
|
+
platform: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Write the service file and return install instructions.
|
|
30
|
+
*/
|
|
31
|
+
export declare function installService(config?: Partial<ServiceConfig>): string[];
|
|
32
|
+
//# sourceMappingURL=service-generator.d.ts.map
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// service-generator.ts — generate systemd/launchd service files for daemon
|
|
2
|
+
// auto-start on boot and crash restart. supports both Linux (systemd) and
|
|
3
|
+
// macOS (launchd) platforms.
|
|
4
|
+
import { writeFileSync, mkdirSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { homedir, platform } from "node:os";
|
|
7
|
+
const DEFAULT_CONFIG = {
|
|
8
|
+
name: "aoaoe",
|
|
9
|
+
description: "aoaoe — autonomous supervisor daemon for agent-of-empires",
|
|
10
|
+
execPath: "aoaoe",
|
|
11
|
+
workingDir: process.cwd(),
|
|
12
|
+
restartSec: 5,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Generate a systemd unit file for Linux.
|
|
16
|
+
*/
|
|
17
|
+
export function generateSystemdUnit(config = {}) {
|
|
18
|
+
const c = { ...DEFAULT_CONFIG, ...config };
|
|
19
|
+
const execStart = c.configPath
|
|
20
|
+
? `${c.execPath} --config ${c.configPath}`
|
|
21
|
+
: c.execPath;
|
|
22
|
+
return `[Unit]
|
|
23
|
+
Description=${c.description}
|
|
24
|
+
After=network.target
|
|
25
|
+
|
|
26
|
+
[Service]
|
|
27
|
+
Type=simple
|
|
28
|
+
ExecStart=${execStart}
|
|
29
|
+
WorkingDirectory=${c.workingDir}
|
|
30
|
+
Restart=on-failure
|
|
31
|
+
RestartSec=${c.restartSec}
|
|
32
|
+
${c.user ? `User=${c.user}` : ""}
|
|
33
|
+
${c.logPath ? `StandardOutput=append:${c.logPath}\nStandardError=append:${c.logPath}` : "StandardOutput=journal\nStandardError=journal"}
|
|
34
|
+
Environment=NODE_ENV=production
|
|
35
|
+
|
|
36
|
+
[Install]
|
|
37
|
+
WantedBy=multi-user.target
|
|
38
|
+
`.replace(/\n{3,}/g, "\n\n").trim() + "\n";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Generate a launchd plist file for macOS.
|
|
42
|
+
*/
|
|
43
|
+
export function generateLaunchdPlist(config = {}) {
|
|
44
|
+
const c = { ...DEFAULT_CONFIG, ...config };
|
|
45
|
+
const logPath = c.logPath ?? join(homedir(), "Library", "Logs", "aoaoe.log");
|
|
46
|
+
const errPath = c.logPath ? c.logPath.replace(".log", ".err.log") : join(homedir(), "Library", "Logs", "aoaoe.err.log");
|
|
47
|
+
const args = c.configPath
|
|
48
|
+
? [`${c.execPath}`, "--config", c.configPath]
|
|
49
|
+
: [c.execPath];
|
|
50
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
51
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
52
|
+
<plist version="1.0">
|
|
53
|
+
<dict>
|
|
54
|
+
<key>Label</key>
|
|
55
|
+
<string>com.aoaoe.daemon</string>
|
|
56
|
+
<key>ProgramArguments</key>
|
|
57
|
+
<array>
|
|
58
|
+
${args.map((a) => ` <string>${escXml(a)}</string>`).join("\n")}
|
|
59
|
+
</array>
|
|
60
|
+
<key>WorkingDirectory</key>
|
|
61
|
+
<string>${escXml(c.workingDir)}</string>
|
|
62
|
+
<key>RunAtLoad</key>
|
|
63
|
+
<true/>
|
|
64
|
+
<key>KeepAlive</key>
|
|
65
|
+
<dict>
|
|
66
|
+
<key>SuccessfulExit</key>
|
|
67
|
+
<false/>
|
|
68
|
+
</dict>
|
|
69
|
+
<key>ThrottleInterval</key>
|
|
70
|
+
<integer>${c.restartSec}</integer>
|
|
71
|
+
<key>StandardOutPath</key>
|
|
72
|
+
<string>${escXml(logPath)}</string>
|
|
73
|
+
<key>StandardErrorPath</key>
|
|
74
|
+
<string>${escXml(errPath)}</string>
|
|
75
|
+
<key>EnvironmentVariables</key>
|
|
76
|
+
<dict>
|
|
77
|
+
<key>NODE_ENV</key>
|
|
78
|
+
<string>production</string>
|
|
79
|
+
</dict>
|
|
80
|
+
</dict>
|
|
81
|
+
</plist>
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Detect the current platform and generate the appropriate service file.
|
|
86
|
+
*/
|
|
87
|
+
export function generateServiceFile(config = {}) {
|
|
88
|
+
const os = platform();
|
|
89
|
+
if (os === "darwin") {
|
|
90
|
+
const filename = "com.aoaoe.daemon.plist";
|
|
91
|
+
const installPath = join(homedir(), "Library", "LaunchAgents", filename);
|
|
92
|
+
return { content: generateLaunchdPlist(config), filename, installPath, platform: "launchd" };
|
|
93
|
+
}
|
|
94
|
+
// Linux + fallback
|
|
95
|
+
const filename = `${config.name ?? "aoaoe"}.service`;
|
|
96
|
+
const installPath = join("/etc", "systemd", "system", filename);
|
|
97
|
+
return { content: generateSystemdUnit(config), filename, installPath, platform: "systemd" };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Write the service file and return install instructions.
|
|
101
|
+
*/
|
|
102
|
+
export function installService(config = {}) {
|
|
103
|
+
const { content, filename, installPath, platform: plat } = generateServiceFile(config);
|
|
104
|
+
const outDir = join(homedir(), ".aoaoe");
|
|
105
|
+
mkdirSync(outDir, { recursive: true });
|
|
106
|
+
const outPath = join(outDir, filename);
|
|
107
|
+
writeFileSync(outPath, content);
|
|
108
|
+
const lines = [];
|
|
109
|
+
lines.push(`Generated ${plat} service file: ${outPath}`);
|
|
110
|
+
lines.push("");
|
|
111
|
+
if (plat === "systemd") {
|
|
112
|
+
lines.push("Install with:");
|
|
113
|
+
lines.push(` sudo cp ${outPath} ${installPath}`);
|
|
114
|
+
lines.push(" sudo systemctl daemon-reload");
|
|
115
|
+
lines.push(` sudo systemctl enable ${filename}`);
|
|
116
|
+
lines.push(` sudo systemctl start ${filename.replace(".service", "")}`);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
lines.push("Install with:");
|
|
120
|
+
lines.push(` cp ${outPath} ${installPath}`);
|
|
121
|
+
lines.push(` launchctl load ${installPath}`);
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push("Uninstall with:");
|
|
124
|
+
lines.push(` launchctl unload ${installPath}`);
|
|
125
|
+
lines.push(` rm ${installPath}`);
|
|
126
|
+
}
|
|
127
|
+
return lines;
|
|
128
|
+
}
|
|
129
|
+
function escXml(s) {
|
|
130
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=service-generator.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ReplayEvent {
|
|
2
|
+
timestamp: number;
|
|
3
|
+
timeLabel: string;
|
|
4
|
+
type: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SessionReplay {
|
|
8
|
+
sessionTitle: string;
|
|
9
|
+
events: ReplayEvent[];
|
|
10
|
+
totalDurationMs: number;
|
|
11
|
+
eventCount: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Build a replay timeline for a session from the audit trail.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildSessionReplay(sessionTitle: string, maxEvents?: number): SessionReplay;
|
|
17
|
+
/**
|
|
18
|
+
* Format replay as a chronological narrative.
|
|
19
|
+
*/
|
|
20
|
+
export declare function formatReplay(replay: SessionReplay): string[];
|
|
21
|
+
/**
|
|
22
|
+
* Generate a summary of the replay (for quick review).
|
|
23
|
+
*/
|
|
24
|
+
export declare function summarizeReplay(replay: SessionReplay): string[];
|
|
25
|
+
//# sourceMappingURL=session-replay.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// session-replay.ts — replay a session's activity timeline step-by-step.
|
|
2
|
+
// reconstructs the session's history from audit trail + fleet snapshots
|
|
3
|
+
// and presents it as a chronological narrative for post-mortem analysis.
|
|
4
|
+
import { readRecentAuditEntries } from "./audit-trail.js";
|
|
5
|
+
/**
|
|
6
|
+
* Build a replay timeline for a session from the audit trail.
|
|
7
|
+
*/
|
|
8
|
+
export function buildSessionReplay(sessionTitle, maxEvents = 200) {
|
|
9
|
+
const all = readRecentAuditEntries(10_000);
|
|
10
|
+
const sessionEvents = all.filter((e) => e.session === sessionTitle ||
|
|
11
|
+
e.detail.toLowerCase().includes(sessionTitle.toLowerCase()));
|
|
12
|
+
const events = sessionEvents.slice(-maxEvents).map((e) => ({
|
|
13
|
+
timestamp: new Date(e.timestamp).getTime(),
|
|
14
|
+
timeLabel: e.timestamp.slice(11, 19),
|
|
15
|
+
type: e.type,
|
|
16
|
+
detail: e.detail,
|
|
17
|
+
}));
|
|
18
|
+
const totalDuration = events.length >= 2
|
|
19
|
+
? events[events.length - 1].timestamp - events[0].timestamp
|
|
20
|
+
: 0;
|
|
21
|
+
return {
|
|
22
|
+
sessionTitle,
|
|
23
|
+
events,
|
|
24
|
+
totalDurationMs: totalDuration,
|
|
25
|
+
eventCount: events.length,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Format replay as a chronological narrative.
|
|
30
|
+
*/
|
|
31
|
+
export function formatReplay(replay) {
|
|
32
|
+
if (replay.events.length === 0) {
|
|
33
|
+
return [` No replay data for "${replay.sessionTitle}" (check audit trail)`];
|
|
34
|
+
}
|
|
35
|
+
const lines = [];
|
|
36
|
+
const durationLabel = replay.totalDurationMs > 0
|
|
37
|
+
? formatDuration(replay.totalDurationMs)
|
|
38
|
+
: "instant";
|
|
39
|
+
lines.push(` Replay: "${replay.sessionTitle}" (${replay.eventCount} events, ${durationLabel})`);
|
|
40
|
+
lines.push("");
|
|
41
|
+
let lastTimestamp = 0;
|
|
42
|
+
for (const e of replay.events) {
|
|
43
|
+
// show time gap between events
|
|
44
|
+
if (lastTimestamp > 0) {
|
|
45
|
+
const gap = e.timestamp - lastTimestamp;
|
|
46
|
+
if (gap > 60_000) {
|
|
47
|
+
lines.push(` ... ${formatDuration(gap)} later ...`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
lastTimestamp = e.timestamp;
|
|
51
|
+
const icon = eventIcon(e.type);
|
|
52
|
+
lines.push(` ${e.timeLabel} ${icon} ${e.type}: ${e.detail}`);
|
|
53
|
+
}
|
|
54
|
+
return lines;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generate a summary of the replay (for quick review).
|
|
58
|
+
*/
|
|
59
|
+
export function summarizeReplay(replay) {
|
|
60
|
+
if (replay.events.length === 0)
|
|
61
|
+
return [` "${replay.sessionTitle}": no events`];
|
|
62
|
+
// count by type
|
|
63
|
+
const typeCounts = new Map();
|
|
64
|
+
for (const e of replay.events) {
|
|
65
|
+
typeCounts.set(e.type, (typeCounts.get(e.type) ?? 0) + 1);
|
|
66
|
+
}
|
|
67
|
+
const lines = [];
|
|
68
|
+
lines.push(` "${replay.sessionTitle}" summary:`);
|
|
69
|
+
lines.push(` Duration: ${formatDuration(replay.totalDurationMs)} Events: ${replay.eventCount}`);
|
|
70
|
+
const sorted = [...typeCounts.entries()].sort((a, b) => b[1] - a[1]);
|
|
71
|
+
for (const [type, count] of sorted) {
|
|
72
|
+
lines.push(` ${eventIcon(type)} ${type}: ${count}`);
|
|
73
|
+
}
|
|
74
|
+
return lines;
|
|
75
|
+
}
|
|
76
|
+
function eventIcon(type) {
|
|
77
|
+
const icons = {
|
|
78
|
+
reasoner_action: "🧠",
|
|
79
|
+
auto_complete: "✅",
|
|
80
|
+
budget_pause: "💰",
|
|
81
|
+
conflict_detected: "⚔",
|
|
82
|
+
operator_command: "👤",
|
|
83
|
+
task_created: "📋",
|
|
84
|
+
task_completed: "🏁",
|
|
85
|
+
session_error: "❌",
|
|
86
|
+
session_restart: "🔄",
|
|
87
|
+
config_change: "⚙",
|
|
88
|
+
stuck_nudge: "👉",
|
|
89
|
+
daemon_start: "🚀",
|
|
90
|
+
daemon_stop: "🛑",
|
|
91
|
+
};
|
|
92
|
+
return icons[type] ?? "·";
|
|
93
|
+
}
|
|
94
|
+
function formatDuration(ms) {
|
|
95
|
+
if (ms < 60_000)
|
|
96
|
+
return `${Math.round(ms / 1000)}s`;
|
|
97
|
+
if (ms < 3_600_000)
|
|
98
|
+
return `${Math.round(ms / 60_000)}m`;
|
|
99
|
+
const h = Math.floor(ms / 3_600_000);
|
|
100
|
+
const m = Math.round((ms % 3_600_000) / 60_000);
|
|
101
|
+
return m > 0 ? `${h}h ${m}m` : `${h}h`;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=session-replay.js.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface WorkflowStage {
|
|
2
|
+
name: string;
|
|
3
|
+
tasks: Array<{
|
|
4
|
+
sessionTitle: string;
|
|
5
|
+
goal: string;
|
|
6
|
+
}>;
|
|
7
|
+
dependsOnStage?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface WorkflowDefinition {
|
|
10
|
+
name: string;
|
|
11
|
+
stages: WorkflowStage[];
|
|
12
|
+
}
|
|
13
|
+
export interface WorkflowState {
|
|
14
|
+
name: string;
|
|
15
|
+
currentStage: number;
|
|
16
|
+
stages: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
status: "pending" | "active" | "completed" | "failed";
|
|
19
|
+
tasks: Array<{
|
|
20
|
+
sessionTitle: string;
|
|
21
|
+
status: string;
|
|
22
|
+
}>;
|
|
23
|
+
}>;
|
|
24
|
+
startedAt: number;
|
|
25
|
+
completedAt?: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a workflow state from a definition.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createWorkflowState(def: WorkflowDefinition, now?: number): WorkflowState;
|
|
31
|
+
/**
|
|
32
|
+
* Advance the workflow based on current task states.
|
|
33
|
+
* Returns actions to take (activate next stage's tasks, complete workflow, etc.)
|
|
34
|
+
*/
|
|
35
|
+
export declare function advanceWorkflow(workflow: WorkflowState, taskStates: ReadonlyMap<string, string>): {
|
|
36
|
+
actions: WorkflowAction[];
|
|
37
|
+
completed: boolean;
|
|
38
|
+
};
|
|
39
|
+
export interface WorkflowAction {
|
|
40
|
+
type: "activate_task" | "stage_started" | "stage_completed" | "stage_failed" | "workflow_completed";
|
|
41
|
+
stage: string;
|
|
42
|
+
detail: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Format workflow state for TUI display.
|
|
46
|
+
*/
|
|
47
|
+
export declare function formatWorkflow(workflow: WorkflowState): string[];
|
|
48
|
+
//# sourceMappingURL=workflow-engine.d.ts.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// workflow-engine.ts — define multi-session workflows with fan-out/fan-in.
|
|
2
|
+
// a workflow is a DAG of stages, each containing one or more parallel tasks.
|
|
3
|
+
// stages execute sequentially; tasks within a stage execute in parallel.
|
|
4
|
+
/**
|
|
5
|
+
* Create a workflow state from a definition.
|
|
6
|
+
*/
|
|
7
|
+
export function createWorkflowState(def, now = Date.now()) {
|
|
8
|
+
return {
|
|
9
|
+
name: def.name,
|
|
10
|
+
currentStage: 0,
|
|
11
|
+
stages: def.stages.map((s) => ({
|
|
12
|
+
name: s.name,
|
|
13
|
+
status: "pending",
|
|
14
|
+
tasks: s.tasks.map((t) => ({ sessionTitle: t.sessionTitle, status: "pending" })),
|
|
15
|
+
})),
|
|
16
|
+
startedAt: now,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Advance the workflow based on current task states.
|
|
21
|
+
* Returns actions to take (activate next stage's tasks, complete workflow, etc.)
|
|
22
|
+
*/
|
|
23
|
+
export function advanceWorkflow(workflow, taskStates) {
|
|
24
|
+
const actions = [];
|
|
25
|
+
if (workflow.currentStage >= workflow.stages.length) {
|
|
26
|
+
return { actions: [], completed: true };
|
|
27
|
+
}
|
|
28
|
+
const current = workflow.stages[workflow.currentStage];
|
|
29
|
+
// update task statuses from live data
|
|
30
|
+
for (const task of current.tasks) {
|
|
31
|
+
const liveStatus = taskStates.get(task.sessionTitle);
|
|
32
|
+
if (liveStatus)
|
|
33
|
+
task.status = liveStatus;
|
|
34
|
+
}
|
|
35
|
+
// check if current stage is completed
|
|
36
|
+
const allDone = current.tasks.every((t) => t.status === "completed");
|
|
37
|
+
const anyFailed = current.tasks.some((t) => t.status === "failed");
|
|
38
|
+
if (anyFailed) {
|
|
39
|
+
current.status = "failed";
|
|
40
|
+
actions.push({ type: "stage_failed", stage: current.name, detail: `stage "${current.name}" has failed tasks` });
|
|
41
|
+
return { actions, completed: false };
|
|
42
|
+
}
|
|
43
|
+
if (allDone) {
|
|
44
|
+
current.status = "completed";
|
|
45
|
+
workflow.currentStage++;
|
|
46
|
+
if (workflow.currentStage >= workflow.stages.length) {
|
|
47
|
+
workflow.completedAt = Date.now();
|
|
48
|
+
actions.push({ type: "workflow_completed", stage: workflow.name, detail: `workflow "${workflow.name}" completed` });
|
|
49
|
+
return { actions, completed: true };
|
|
50
|
+
}
|
|
51
|
+
// activate next stage
|
|
52
|
+
const next = workflow.stages[workflow.currentStage];
|
|
53
|
+
next.status = "active";
|
|
54
|
+
for (const task of next.tasks) {
|
|
55
|
+
actions.push({ type: "activate_task", stage: next.name, detail: task.sessionTitle });
|
|
56
|
+
}
|
|
57
|
+
actions.push({ type: "stage_started", stage: next.name, detail: `stage "${next.name}" started (${next.tasks.length} tasks)` });
|
|
58
|
+
}
|
|
59
|
+
else if (current.status === "pending") {
|
|
60
|
+
// activate first stage
|
|
61
|
+
current.status = "active";
|
|
62
|
+
for (const task of current.tasks) {
|
|
63
|
+
actions.push({ type: "activate_task", stage: current.name, detail: task.sessionTitle });
|
|
64
|
+
}
|
|
65
|
+
actions.push({ type: "stage_started", stage: current.name, detail: `stage "${current.name}" started (${current.tasks.length} tasks)` });
|
|
66
|
+
}
|
|
67
|
+
return { actions, completed: false };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Format workflow state for TUI display.
|
|
71
|
+
*/
|
|
72
|
+
export function formatWorkflow(workflow) {
|
|
73
|
+
const lines = [];
|
|
74
|
+
const elapsed = workflow.completedAt
|
|
75
|
+
? workflow.completedAt - workflow.startedAt
|
|
76
|
+
: Date.now() - workflow.startedAt;
|
|
77
|
+
const duration = elapsed < 60_000 ? `${Math.round(elapsed / 1000)}s` : `${Math.round(elapsed / 60_000)}m`;
|
|
78
|
+
lines.push(` Workflow: ${workflow.name} (${duration})`);
|
|
79
|
+
for (let i = 0; i < workflow.stages.length; i++) {
|
|
80
|
+
const s = workflow.stages[i];
|
|
81
|
+
const icon = s.status === "completed" ? "✓" : s.status === "active" ? "▶" : s.status === "failed" ? "✗" : "○";
|
|
82
|
+
const current = i === workflow.currentStage ? " ←" : "";
|
|
83
|
+
lines.push(` ${icon} Stage ${i + 1}: ${s.name} (${s.status})${current}`);
|
|
84
|
+
for (const t of s.tasks) {
|
|
85
|
+
const tIcon = t.status === "completed" ? "✓" : t.status === "active" ? "~" : t.status === "failed" ? "!" : ".";
|
|
86
|
+
lines.push(` [${tIcon}] ${t.sessionTitle}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=workflow-engine.js.map
|