tryassay 0.2.0 → 0.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/dist/cli.js +21 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/runtime.d.ts +12 -0
- package/dist/commands/runtime.js +73 -6
- package/dist/commands/runtime.js.map +1 -1
- package/dist/runtime/agent-loop.d.ts +10 -1
- package/dist/runtime/agent-loop.js +113 -30
- package/dist/runtime/agent-loop.js.map +1 -1
- package/dist/runtime/audit-log.d.ts +35 -0
- package/dist/runtime/audit-log.js +115 -0
- package/dist/runtime/audit-log.js.map +1 -0
- package/dist/runtime/config-loader.d.ts +41 -0
- package/dist/runtime/config-loader.js +116 -0
- package/dist/runtime/config-loader.js.map +1 -0
- package/dist/runtime/control-server.d.ts +25 -0
- package/dist/runtime/control-server.js +83 -0
- package/dist/runtime/control-server.js.map +1 -0
- package/dist/runtime/logger.d.ts +20 -0
- package/dist/runtime/logger.js +73 -0
- package/dist/runtime/logger.js.map +1 -0
- package/dist/runtime/observer.d.ts +5 -1
- package/dist/runtime/observer.js +61 -14
- package/dist/runtime/observer.js.map +1 -1
- package/dist/runtime/reasoner.d.ts +2 -2
- package/dist/runtime/reasoner.js +18 -7
- package/dist/runtime/reasoner.js.map +1 -1
- package/dist/runtime/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,7 +10,7 @@ import { regenerateCommand } from './commands/regenerate.js';
|
|
|
10
10
|
import { remediateCommand } from './commands/remediate.js';
|
|
11
11
|
import { reverseCommand } from './commands/reverse.js';
|
|
12
12
|
import { assessCommand } from './commands/assess.js';
|
|
13
|
-
import { runtimeCommand } from './commands/runtime.js';
|
|
13
|
+
import { runtimeCommand, runtimeInitCommand, runtimeStatusCommand, runtimeStopCommand } from './commands/runtime.js';
|
|
14
14
|
const program = new Command();
|
|
15
15
|
program
|
|
16
16
|
.name('assay')
|
|
@@ -92,6 +92,26 @@ runtime
|
|
|
92
92
|
.option('-c, --commands <cmds...>', 'Allowed shell commands')
|
|
93
93
|
.option('-u, --urls <urls...>', 'Allowed URLs for API calls')
|
|
94
94
|
.option('-m, --model <model>', 'Model ID for reasoning', 'claude-sonnet-4-5-20250929')
|
|
95
|
+
.option('--control-port <port>', 'HTTP control server port', '3700')
|
|
96
|
+
.option('--json', 'Output structured JSON logs to stdout')
|
|
97
|
+
.option('--max-cost <cents>', 'Max session cost in cents before auto-pause', '500')
|
|
95
98
|
.action(runtimeCommand);
|
|
99
|
+
runtime
|
|
100
|
+
.command('init')
|
|
101
|
+
.description('Generate a default .assay/agent.json config file')
|
|
102
|
+
.argument('[path]', 'Path to the project directory', '.')
|
|
103
|
+
.action(runtimeInitCommand);
|
|
104
|
+
runtime
|
|
105
|
+
.command('status')
|
|
106
|
+
.description('Query the running agent status via HTTP control server')
|
|
107
|
+
.option('--port <port>', 'Control server port', '3700')
|
|
108
|
+
.option('--token <token>', 'Bearer token (printed at agent startup)')
|
|
109
|
+
.action(runtimeStatusCommand);
|
|
110
|
+
runtime
|
|
111
|
+
.command('stop')
|
|
112
|
+
.description('Stop a running agent via HTTP control server')
|
|
113
|
+
.option('--port <port>', 'Control server port', '3700')
|
|
114
|
+
.option('--token <token>', 'Bearer token (printed at agent startup)')
|
|
115
|
+
.action(runtimeStopCommand);
|
|
96
116
|
program.parse();
|
|
97
117
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAErH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,0DAA0D,CAAC;KACvE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,sDAAsD,CAAC;KACnE,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,aAAa,CAAC;KACtB,WAAW,CAAC,uDAAuD,CAAC;KACpE,MAAM,CAAC,mBAAmB,EAAE,2CAA2C,EAAE,KAAK,CAAC;KAC/E,MAAM,CAAC,kBAAkB,CAAC,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,UAAU,CAAC;KACnB,WAAW,CAAC,oDAAoD,CAAC;KACjE,MAAM,CAAC,oBAAoB,EAAE,iBAAiB,CAAC;KAC/C,MAAM,CAAC,eAAe,CAAC,CAAC;AAE3B,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,mEAAmE,CAAC;KAChF,MAAM,CAAC,0BAA0B,EAAE,uCAAuC,CAAC;KAC3E,MAAM,CAAC,yBAAyB,EAAE,uDAAuD,CAAC;KAC1F,MAAM,CAAC,cAAc,CAAC,CAAC;AAE1B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,mBAAmB,EAAE,wCAAwC,EAAE,GAAG,CAAC;KAC1E,MAAM,CAAC,0BAA0B,EAAE,uCAAuC,CAAC;KAC3E,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,iDAAiD,CAAC;KAC9D,MAAM,CAAC,0BAA0B,EAAE,uCAAuC,CAAC;KAC3E,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,OAAO;KACJ,OAAO,CAAC,YAAY,CAAC;KACrB,WAAW,CAAC,0EAA0E,CAAC;KACvF,MAAM,CAAC,0BAA0B,EAAE,0DAA0D,CAAC;KAC9F,MAAM,CAAC,iBAAiB,CAAC,CAAC;AAE7B,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,qFAAqF,CAAC;KAClG,MAAM,CAAC,0BAA0B,EAAE,uCAAuC,CAAC;KAC3E,MAAM,CAAC,mBAAmB,EAAE,mCAAmC,EAAE,GAAG,CAAC;KACrE,MAAM,CAAC,0BAA0B,EAAE,oCAAoC,EAAE,IAAI,CAAC;KAC9E,MAAM,CAAC,gBAAgB,CAAC,CAAC;AAE5B,OAAO;KACJ,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,6DAA6D,CAAC;KAC1E,MAAM,CAAC,mBAAmB,EAAE,yBAAyB,CAAC;KACtD,MAAM,CAAC,wBAAwB,EAAE,qBAAqB,CAAC;KACvD,MAAM,CAAC,uBAAuB,EAAE,iBAAiB,EAAE,YAAY,CAAC;KAChE,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;KACjD,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC;KACjD,MAAM,CAAC,cAAc,CAAC,CAAC;AAE1B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,yDAAyD,CAAC;KACtE,QAAQ,CAAC,UAAU,EAAE,qCAAqC,CAAC;KAC3D,MAAM,CAAC,WAAW,EAAE,+BAA+B,CAAC;KACpD,MAAM,CAAC,oBAAoB,EAAE,kDAAkD,CAAC;KAChF,MAAM,CAAC,mBAAmB,EAAE,gCAAgC,EAAE,GAAG,CAAC;KAClE,MAAM,CAAC,iBAAiB,EAAE,wBAAwB,EAAE,qBAAqB,CAAC;KAC1E,MAAM,CAAC,aAAa,CAAC,CAAC;AAEzB,gEAAgE;AAEhE,MAAM,OAAO,GAAG,OAAO;KACpB,OAAO,CAAC,SAAS,CAAC;KAClB,WAAW,CAAC,+DAA+D,CAAC,CAAC;AAEhF,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kCAAkC,CAAC;KAC/C,QAAQ,CAAC,QAAQ,EAAE,+BAA+B,CAAC;KACnD,MAAM,CAAC,mBAAmB,EAAE,YAAY,CAAC;KACzC,MAAM,CAAC,wBAAwB,EAAE,uCAAuC,CAAC;KACzE,MAAM,CAAC,2BAA2B,EAAE,oCAAoC,CAAC;KACzE,MAAM,CAAC,uBAAuB,EAAE,0CAA0C,CAAC;KAC3E,MAAM,CAAC,0BAA0B,EAAE,wBAAwB,CAAC;KAC5D,MAAM,CAAC,sBAAsB,EAAE,4BAA4B,CAAC;KAC5D,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,EAAE,4BAA4B,CAAC;KACrF,MAAM,CAAC,uBAAuB,EAAE,0BAA0B,EAAE,MAAM,CAAC;KACnE,MAAM,CAAC,QAAQ,EAAE,uCAAuC,CAAC;KACzD,MAAM,CAAC,oBAAoB,EAAE,6CAA6C,EAAE,KAAK,CAAC;KAClF,MAAM,CAAC,cAAc,CAAC,CAAC;AAE1B,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,kDAAkD,CAAC;KAC/D,QAAQ,CAAC,QAAQ,EAAE,+BAA+B,EAAE,GAAG,CAAC;KACxD,MAAM,CAAC,kBAAkB,CAAC,CAAC;AAE9B,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,wDAAwD,CAAC;KACrE,MAAM,CAAC,eAAe,EAAE,qBAAqB,EAAE,MAAM,CAAC;KACtD,MAAM,CAAC,iBAAiB,EAAE,yCAAyC,CAAC;KACpE,MAAM,CAAC,oBAAoB,CAAC,CAAC;AAEhC,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,8CAA8C,CAAC;KAC3D,MAAM,CAAC,eAAe,EAAE,qBAAqB,EAAE,MAAM,CAAC;KACtD,MAAM,CAAC,iBAAiB,EAAE,yCAAyC,CAAC;KACpE,MAAM,CAAC,kBAAkB,CAAC,CAAC;AAE9B,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -6,6 +6,18 @@ interface RuntimeCommandOptions {
|
|
|
6
6
|
commands?: string[];
|
|
7
7
|
urls?: string[];
|
|
8
8
|
model?: string;
|
|
9
|
+
controlPort?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
maxCost?: string;
|
|
9
12
|
}
|
|
10
13
|
export declare function runtimeCommand(targetPath: string, opts: RuntimeCommandOptions): Promise<void>;
|
|
14
|
+
export declare function runtimeInitCommand(targetPath: string): Promise<void>;
|
|
15
|
+
export declare function runtimeStatusCommand(opts: {
|
|
16
|
+
port?: string;
|
|
17
|
+
token?: string;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export declare function runtimeStopCommand(opts: {
|
|
20
|
+
port?: string;
|
|
21
|
+
token?: string;
|
|
22
|
+
}): Promise<void>;
|
|
11
23
|
export {};
|
package/dist/commands/runtime.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// ============================================================
|
|
2
|
-
// CLI Command: tryassay runtime start <path>
|
|
3
|
-
// Launches the Assay Verified Agent Runtime
|
|
2
|
+
// CLI Command: tryassay runtime start|init|status|stop <path>
|
|
3
|
+
// Launches and manages the Assay Verified Agent Runtime
|
|
4
4
|
// ============================================================
|
|
5
5
|
import { AgentLoop, buildAgentConfig } from '../runtime/agent-loop.js';
|
|
6
|
+
import { loadAgentConfigFile, mergeConfigFile, generateDefaultConfig } from '../runtime/config-loader.js';
|
|
6
7
|
export async function runtimeCommand(targetPath, opts) {
|
|
7
8
|
console.log('');
|
|
8
9
|
console.log(' Assay Verified Agent Runtime');
|
|
@@ -16,22 +17,34 @@ export async function runtimeCommand(targetPath, opts) {
|
|
|
16
17
|
commands: opts.commands,
|
|
17
18
|
urls: opts.urls,
|
|
18
19
|
modelId: opts.model,
|
|
20
|
+
controlPort: opts.controlPort ? parseInt(opts.controlPort, 10) : undefined,
|
|
21
|
+
jsonLogs: opts.json,
|
|
22
|
+
maxSessionCostCents: opts.maxCost ? parseInt(opts.maxCost, 10) : undefined,
|
|
19
23
|
};
|
|
20
|
-
|
|
24
|
+
// Build default config from CLI args
|
|
25
|
+
let config = buildAgentConfig(targetPath, runtimeOpts);
|
|
26
|
+
// Check for .assay/agent.json and merge if present
|
|
27
|
+
const fileConfig = await loadAgentConfigFile(targetPath);
|
|
28
|
+
if (fileConfig) {
|
|
29
|
+
config = mergeConfigFile(config, fileConfig);
|
|
30
|
+
console.log(' Config: loaded from .assay/agent.json');
|
|
31
|
+
}
|
|
21
32
|
console.log(` Agent: ${config.name}`);
|
|
22
33
|
console.log(` Scope: ${config.scope.allowedDirectories.join(', ')}`);
|
|
23
34
|
console.log(` Model: ${config.modelId}`);
|
|
24
35
|
console.log(` Signals: ${config.signals.map(s => s.type).join(', ')}`);
|
|
25
36
|
console.log(` Experiences: ${config.experienceStorePath}`);
|
|
37
|
+
console.log(` Control port: ${config.controlPort ?? 'disabled'}`);
|
|
38
|
+
console.log(` Cost limit: ${config.limits.maxSessionCostCents ?? 'none'}c`);
|
|
26
39
|
console.log('');
|
|
27
40
|
console.log(' Pipeline: observe -> reason -> plan -> verify -> execute -> reflect');
|
|
28
41
|
console.log(' Press Ctrl+C to stop');
|
|
29
42
|
console.log('');
|
|
30
43
|
const agent = new AgentLoop(config);
|
|
31
44
|
// Log audit events
|
|
32
|
-
agent.on('audit', (
|
|
33
|
-
// Audit events are
|
|
34
|
-
// This hook is for future
|
|
45
|
+
agent.on('audit', (_entry) => {
|
|
46
|
+
// Audit events are now persisted by the AuditLog class
|
|
47
|
+
// This hook is for future external integrations
|
|
35
48
|
});
|
|
36
49
|
// Handle approval requests
|
|
37
50
|
agent.on('approval_needed', (info) => {
|
|
@@ -60,4 +73,58 @@ export async function runtimeCommand(targetPath, opts) {
|
|
|
60
73
|
// Start the agent loop (blocks until stopped)
|
|
61
74
|
await agent.start();
|
|
62
75
|
}
|
|
76
|
+
export async function runtimeInitCommand(targetPath) {
|
|
77
|
+
const configPath = await generateDefaultConfig(targetPath);
|
|
78
|
+
console.log(` Generated default agent config: ${configPath}`);
|
|
79
|
+
console.log(' Edit this file to customize your agent, then run: tryassay runtime start .');
|
|
80
|
+
}
|
|
81
|
+
export async function runtimeStatusCommand(opts) {
|
|
82
|
+
const port = opts.port ? parseInt(opts.port, 10) : 3700;
|
|
83
|
+
const token = opts.token;
|
|
84
|
+
if (!token) {
|
|
85
|
+
console.error(' Error: --token is required. The token is printed when the agent starts.');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(`http://localhost:${port}/status`, {
|
|
90
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
91
|
+
});
|
|
92
|
+
if (!response.ok) {
|
|
93
|
+
console.error(` Error: HTTP ${response.status} ${response.statusText}`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const state = await response.json();
|
|
97
|
+
console.log(JSON.stringify(state, null, 2));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
101
|
+
console.error(` Error: Could not connect to agent on port ${port}: ${msg}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export async function runtimeStopCommand(opts) {
|
|
106
|
+
const port = opts.port ? parseInt(opts.port, 10) : 3700;
|
|
107
|
+
const token = opts.token;
|
|
108
|
+
if (!token) {
|
|
109
|
+
console.error(' Error: --token is required. The token is printed when the agent starts.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const response = await fetch(`http://localhost:${port}/stop`, {
|
|
114
|
+
method: 'POST',
|
|
115
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
116
|
+
});
|
|
117
|
+
if (!response.ok) {
|
|
118
|
+
console.error(` Error: HTTP ${response.status} ${response.statusText}`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const result = await response.json();
|
|
122
|
+
console.log(` Agent stopping: ${JSON.stringify(result)}`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
+
console.error(` Error: Could not connect to agent on port ${port}: ${msg}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
63
130
|
//# sourceMappingURL=runtime.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../../src/commands/runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,9 +9,13 @@ export declare class AgentLoop extends EventEmitter {
|
|
|
9
9
|
private executor;
|
|
10
10
|
private reflector;
|
|
11
11
|
private experienceStore;
|
|
12
|
+
private auditLog;
|
|
13
|
+
private logger;
|
|
14
|
+
private controlServer;
|
|
12
15
|
private state;
|
|
13
16
|
private startTime;
|
|
14
17
|
private hourWindow;
|
|
18
|
+
private sessionCostCents;
|
|
15
19
|
private running;
|
|
16
20
|
private paused;
|
|
17
21
|
private pauseResolve;
|
|
@@ -20,16 +24,18 @@ export declare class AgentLoop extends EventEmitter {
|
|
|
20
24
|
stop(): Promise<void>;
|
|
21
25
|
pause(): void;
|
|
22
26
|
resume(): void;
|
|
27
|
+
getCostCents(): number;
|
|
23
28
|
getState(): AgentState;
|
|
24
29
|
private tick;
|
|
25
30
|
private registerAdapters;
|
|
26
31
|
private setPhase;
|
|
27
32
|
private checkHourWindow;
|
|
28
33
|
private trackTokens;
|
|
34
|
+
/** Estimate cost in cents from token usage (rough Sonnet pricing). */
|
|
35
|
+
private trackCost;
|
|
29
36
|
private recordError;
|
|
30
37
|
private audit;
|
|
31
38
|
private describePayload;
|
|
32
|
-
private log;
|
|
33
39
|
private sleep;
|
|
34
40
|
}
|
|
35
41
|
export interface RuntimeOptions {
|
|
@@ -42,5 +48,8 @@ export interface RuntimeOptions {
|
|
|
42
48
|
blocked?: string[];
|
|
43
49
|
modelId?: string;
|
|
44
50
|
experiencesPath?: string;
|
|
51
|
+
controlPort?: number;
|
|
52
|
+
jsonLogs?: boolean;
|
|
53
|
+
maxSessionCostCents?: number;
|
|
45
54
|
}
|
|
46
55
|
export declare function buildAgentConfig(targetPath: string, opts?: RuntimeOptions): AgentConfig;
|
|
@@ -5,13 +5,16 @@
|
|
|
5
5
|
// ============================================================
|
|
6
6
|
import { EventEmitter } from 'node:events';
|
|
7
7
|
import { randomUUID } from 'node:crypto';
|
|
8
|
-
import { resolve } from 'node:path';
|
|
8
|
+
import { resolve, join } from 'node:path';
|
|
9
9
|
import { Observer, FileWatcherAdapter, WebhookAdapter, ScheduleAdapter, } from './observer.js';
|
|
10
10
|
import { Reasoner } from './reasoner.js';
|
|
11
11
|
import { Planner } from './planner.js';
|
|
12
12
|
import { RuntimeVerifier } from './verifier.js';
|
|
13
13
|
import { Executor } from './executor.js';
|
|
14
14
|
import { Reflector, ExperienceStore } from './reflector.js';
|
|
15
|
+
import { AuditLog } from './audit-log.js';
|
|
16
|
+
import { Logger } from './logger.js';
|
|
17
|
+
import { ControlServer } from './control-server.js';
|
|
15
18
|
// ── Agent Loop ──────────────────────────────────────────────
|
|
16
19
|
export class AgentLoop extends EventEmitter {
|
|
17
20
|
config;
|
|
@@ -22,6 +25,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
22
25
|
executor;
|
|
23
26
|
reflector;
|
|
24
27
|
experienceStore;
|
|
28
|
+
auditLog;
|
|
29
|
+
logger;
|
|
30
|
+
controlServer = null;
|
|
25
31
|
state;
|
|
26
32
|
startTime = 0;
|
|
27
33
|
hourWindow = {
|
|
@@ -29,12 +35,15 @@ export class AgentLoop extends EventEmitter {
|
|
|
29
35
|
actions: 0,
|
|
30
36
|
tokens: 0,
|
|
31
37
|
};
|
|
38
|
+
sessionCostCents = 0;
|
|
32
39
|
running = false;
|
|
33
40
|
paused = false;
|
|
34
41
|
pauseResolve = null;
|
|
35
42
|
constructor(config) {
|
|
36
43
|
super();
|
|
37
44
|
this.config = config;
|
|
45
|
+
// Derive .assay directory from experience store path
|
|
46
|
+
const assayDir = resolve(config.experienceStorePath, '..');
|
|
38
47
|
// Initialize components
|
|
39
48
|
this.observer = new Observer();
|
|
40
49
|
this.reasoner = new Reasoner();
|
|
@@ -43,6 +52,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
43
52
|
this.executor = new Executor(config.scope);
|
|
44
53
|
this.reflector = new Reflector();
|
|
45
54
|
this.experienceStore = new ExperienceStore(resolve(config.experienceStorePath));
|
|
55
|
+
this.auditLog = new AuditLog(join(assayDir, 'audit.ndjson'));
|
|
56
|
+
this.logger = new Logger({
|
|
57
|
+
jsonMode: config.jsonLogs ?? false,
|
|
58
|
+
logFilePath: join(assayDir, 'agent.log'),
|
|
59
|
+
});
|
|
60
|
+
// Initialize control server if port is configured
|
|
61
|
+
if (config.controlPort) {
|
|
62
|
+
this.controlServer = new ControlServer({
|
|
63
|
+
getState: () => this.getState(),
|
|
64
|
+
pause: () => this.pause(),
|
|
65
|
+
resume: () => this.resume(),
|
|
66
|
+
stop: () => this.stop(),
|
|
67
|
+
getCostCents: () => this.sessionCostCents,
|
|
68
|
+
}, { port: config.controlPort });
|
|
69
|
+
}
|
|
46
70
|
// Initialize state
|
|
47
71
|
this.state = {
|
|
48
72
|
phase: 'idle',
|
|
@@ -66,12 +90,19 @@ export class AgentLoop extends EventEmitter {
|
|
|
66
90
|
return;
|
|
67
91
|
this.running = true;
|
|
68
92
|
this.startTime = Date.now();
|
|
93
|
+
this.sessionCostCents = 0;
|
|
69
94
|
this.hourWindow = { start: Date.now(), actions: 0, tokens: 0 };
|
|
70
95
|
this.setPhase('observing');
|
|
71
96
|
this.audit('agent_started', { name: this.config.name });
|
|
72
|
-
this.
|
|
73
|
-
this.
|
|
74
|
-
this.
|
|
97
|
+
this.logger.info(`Agent "${this.config.name}" started`);
|
|
98
|
+
this.logger.info(` Watching: ${this.config.scope.allowedDirectories.join(', ')}`);
|
|
99
|
+
this.logger.info(` Signals: ${this.config.signals.map(s => s.type).join(', ')}`);
|
|
100
|
+
// Start control server
|
|
101
|
+
if (this.controlServer) {
|
|
102
|
+
await this.controlServer.start();
|
|
103
|
+
this.logger.info(` Control: http://localhost:${this.config.controlPort}/status`);
|
|
104
|
+
this.logger.info(` Token: ${this.controlServer.getToken()}`);
|
|
105
|
+
}
|
|
75
106
|
await this.observer.start();
|
|
76
107
|
// Main loop
|
|
77
108
|
while (this.running) {
|
|
@@ -81,15 +112,22 @@ export class AgentLoop extends EventEmitter {
|
|
|
81
112
|
catch (err) {
|
|
82
113
|
const message = err instanceof Error ? err.message : String(err);
|
|
83
114
|
this.recordError('observing', message, true);
|
|
84
|
-
this.
|
|
115
|
+
this.logger.error(`Loop error (recoverable): ${message}`);
|
|
85
116
|
// Brief pause before retrying
|
|
86
117
|
await this.sleep(1000);
|
|
87
118
|
}
|
|
88
119
|
}
|
|
89
120
|
await this.observer.stop();
|
|
121
|
+
// Stop control server
|
|
122
|
+
if (this.controlServer) {
|
|
123
|
+
await this.controlServer.stop();
|
|
124
|
+
}
|
|
90
125
|
this.setPhase('stopped');
|
|
91
|
-
this.audit('agent_stopped', {
|
|
92
|
-
|
|
126
|
+
this.audit('agent_stopped', {
|
|
127
|
+
uptime: Date.now() - this.startTime,
|
|
128
|
+
totalCostCents: this.sessionCostCents,
|
|
129
|
+
});
|
|
130
|
+
this.logger.info(`Agent "${this.config.name}" stopped (cost: ${this.sessionCostCents.toFixed(2)}c)`);
|
|
93
131
|
}
|
|
94
132
|
async stop() {
|
|
95
133
|
this.running = false;
|
|
@@ -98,7 +136,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
98
136
|
pause() {
|
|
99
137
|
this.paused = true;
|
|
100
138
|
this.setPhase('paused');
|
|
101
|
-
this.
|
|
139
|
+
this.logger.info('Agent paused');
|
|
102
140
|
}
|
|
103
141
|
resume() {
|
|
104
142
|
if (!this.paused)
|
|
@@ -108,7 +146,10 @@ export class AgentLoop extends EventEmitter {
|
|
|
108
146
|
this.pauseResolve();
|
|
109
147
|
this.pauseResolve = null;
|
|
110
148
|
}
|
|
111
|
-
this.
|
|
149
|
+
this.logger.info('Agent resumed');
|
|
150
|
+
}
|
|
151
|
+
getCostCents() {
|
|
152
|
+
return this.sessionCostCents;
|
|
112
153
|
}
|
|
113
154
|
getState() {
|
|
114
155
|
return {
|
|
@@ -133,10 +174,17 @@ export class AgentLoop extends EventEmitter {
|
|
|
133
174
|
this.checkHourWindow();
|
|
134
175
|
// Check rate limits
|
|
135
176
|
if (this.hourWindow.actions >= this.config.limits.maxActionsPerHour) {
|
|
136
|
-
this.
|
|
177
|
+
this.logger.warn('Hourly action limit reached, waiting...');
|
|
137
178
|
await this.sleep(60_000);
|
|
138
179
|
return;
|
|
139
180
|
}
|
|
181
|
+
// Check cost limits
|
|
182
|
+
const maxCost = this.config.limits.maxSessionCostCents;
|
|
183
|
+
if (maxCost && this.sessionCostCents >= maxCost) {
|
|
184
|
+
this.logger.warn(`Session cost limit reached (${this.sessionCostCents.toFixed(2)}c / ${maxCost}c), pausing...`);
|
|
185
|
+
this.pause();
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
140
188
|
// Phase 1: Observe — wait for next observation
|
|
141
189
|
this.setPhase('observing');
|
|
142
190
|
const observation = await this.observer.dequeue();
|
|
@@ -149,25 +197,33 @@ export class AgentLoop extends EventEmitter {
|
|
|
149
197
|
source: observation.source,
|
|
150
198
|
urgency: observation.urgency,
|
|
151
199
|
});
|
|
152
|
-
this.
|
|
200
|
+
this.logger.info(`Observation: [${observation.urgency}] ${observation.source} — ${this.describePayload(observation)}`);
|
|
201
|
+
// Fetch relevant past experiences before reasoning
|
|
202
|
+
const contextString = `${observation.source} ${JSON.stringify(observation.payload).slice(0, 200)}`;
|
|
203
|
+
const relevantExperiences = await this.experienceStore.getRelevantExperiences(contextString, 3);
|
|
153
204
|
// Phase 2: Reason — decide what to do
|
|
154
205
|
this.setPhase('reasoning');
|
|
155
|
-
const decision = await this.reasoner.reason(observation, this.config, this.getState());
|
|
206
|
+
const decision = await this.reasoner.reason(observation, this.config, this.getState(), relevantExperiences.length > 0 ? relevantExperiences : undefined);
|
|
156
207
|
this.state.currentDecision = decision;
|
|
157
|
-
|
|
208
|
+
const reasoningTokens = decision.modelUsage.inputTokens + decision.modelUsage.outputTokens;
|
|
209
|
+
this.trackTokens(reasoningTokens);
|
|
210
|
+
this.trackCost(reasoningTokens);
|
|
158
211
|
this.audit('decision_made', {
|
|
159
212
|
id: decision.id,
|
|
160
213
|
action: decision.action,
|
|
161
214
|
confidence: decision.confidence,
|
|
215
|
+
experiencesUsed: relevantExperiences.length,
|
|
216
|
+
});
|
|
217
|
+
this.logger.info(`Decision: ${decision.action} (confidence: ${decision.confidence.toFixed(2)})`, {
|
|
218
|
+
costCents: this.sessionCostCents,
|
|
162
219
|
});
|
|
163
|
-
this.log(`Decision: ${decision.action} (confidence: ${decision.confidence.toFixed(2)})`);
|
|
164
220
|
// If ignore or defer, skip the rest
|
|
165
221
|
if (decision.action === 'ignore') {
|
|
166
|
-
this.
|
|
222
|
+
this.logger.info(' Action: ignore — skipping');
|
|
167
223
|
return;
|
|
168
224
|
}
|
|
169
225
|
if (decision.action === 'defer') {
|
|
170
|
-
this.
|
|
226
|
+
this.logger.info(` Action: defer — "${decision.reasoning}"`);
|
|
171
227
|
return;
|
|
172
228
|
}
|
|
173
229
|
// Phase 3: Plan — decompose into steps
|
|
@@ -179,7 +235,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
179
235
|
totalSteps: plan.totalSteps,
|
|
180
236
|
overallRisk: plan.overallRisk,
|
|
181
237
|
});
|
|
182
|
-
this.
|
|
238
|
+
this.logger.info(`Plan: ${plan.totalSteps} steps, risk: ${plan.overallRisk}`);
|
|
183
239
|
// Phase 4: Verify — pre-execution safety check
|
|
184
240
|
this.setPhase('verifying');
|
|
185
241
|
const verification = await this.verifier.verifyPlan(plan);
|
|
@@ -190,11 +246,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
190
246
|
passedClaims: verification.passedClaims,
|
|
191
247
|
failedClaims: verification.failedClaims,
|
|
192
248
|
});
|
|
193
|
-
this.
|
|
249
|
+
this.logger.info(`Verification: ${verification.overallVerdict} (${verification.passedClaims}/${verification.totalClaims} claims passed)`);
|
|
194
250
|
// Handle escalation — pause and wait for approval
|
|
195
251
|
if (verification.overallVerdict === 'escalate') {
|
|
196
252
|
this.setPhase('awaiting_approval');
|
|
197
|
-
this.
|
|
253
|
+
this.logger.warn(' Escalated — waiting for human approval');
|
|
198
254
|
this.audit('approval_requested', {
|
|
199
255
|
planId: plan.id,
|
|
200
256
|
escalatedSteps: verification.escalatedSteps,
|
|
@@ -214,7 +270,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
214
270
|
}
|
|
215
271
|
// Handle failure — skip execution, still reflect
|
|
216
272
|
if (verification.overallVerdict === 'fail') {
|
|
217
|
-
this.
|
|
273
|
+
this.logger.warn(' Verification FAILED — skipping execution');
|
|
218
274
|
// Still reflect on the failure
|
|
219
275
|
const experience = this.reflector.reflect(observation, decision, plan, verification, {
|
|
220
276
|
planId: plan.id,
|
|
@@ -235,10 +291,10 @@ export class AgentLoop extends EventEmitter {
|
|
|
235
291
|
}
|
|
236
292
|
// Phase 5: Execute — run verified steps
|
|
237
293
|
this.setPhase('executing');
|
|
238
|
-
this.
|
|
294
|
+
this.logger.info(' Executing plan...');
|
|
239
295
|
const execution = await this.executor.execute(plan, verification);
|
|
240
296
|
this.hourWindow.actions++;
|
|
241
|
-
this.
|
|
297
|
+
this.logger.info(` Execution: ${execution.status} (${execution.completedSteps}/${execution.totalSteps} steps)`);
|
|
242
298
|
for (const se of execution.stepExecutions) {
|
|
243
299
|
const step = plan.steps.find(s => s.id === se.stepId);
|
|
244
300
|
const desc = step?.description ?? se.stepId;
|
|
@@ -248,19 +304,35 @@ export class AgentLoop extends EventEmitter {
|
|
|
248
304
|
durationMs: se.durationMs,
|
|
249
305
|
});
|
|
250
306
|
if (se.status !== 'success') {
|
|
251
|
-
this.
|
|
307
|
+
this.logger.warn(` [${se.status}] ${desc}${se.error ? ': ' + se.error : ''}`);
|
|
252
308
|
}
|
|
253
309
|
else {
|
|
254
|
-
this.
|
|
310
|
+
this.logger.info(` [ok] ${desc} (${se.durationMs}ms)`);
|
|
255
311
|
}
|
|
256
312
|
}
|
|
257
313
|
// Invalidate codebase index after code changes
|
|
258
314
|
if (plan.steps.some(s => s.operation.type === 'code_write')) {
|
|
259
315
|
this.verifier.invalidateIndex();
|
|
260
316
|
}
|
|
261
|
-
// Phase
|
|
317
|
+
// Phase 5.5: Post-execution verification
|
|
318
|
+
const postVerification = await this.verifier.verifyPostExecution(plan, execution);
|
|
319
|
+
this.audit('verification_completed', {
|
|
320
|
+
planId: plan.id,
|
|
321
|
+
stage: 'post_execution',
|
|
322
|
+
verdict: postVerification.overallVerdict,
|
|
323
|
+
totalClaims: postVerification.totalClaims,
|
|
324
|
+
passedClaims: postVerification.passedClaims,
|
|
325
|
+
failedClaims: postVerification.failedClaims,
|
|
326
|
+
});
|
|
327
|
+
this.logger.info(` Post-exec verification: ${postVerification.overallVerdict} (${postVerification.passedClaims}/${postVerification.totalClaims})`);
|
|
328
|
+
// Phase 6: Reflect — capture experience (include post-verification outcome)
|
|
262
329
|
this.setPhase('reflecting');
|
|
263
330
|
const experience = this.reflector.reflect(observation, decision, plan, verification, execution);
|
|
331
|
+
// Override outcome if post-execution verification failed
|
|
332
|
+
if (postVerification.overallVerdict === 'fail' && experience.outcome === 'success') {
|
|
333
|
+
experience.outcome = 'partial_success';
|
|
334
|
+
experience.lessons.push('Post-execution verification failed — execution may not have achieved intended results');
|
|
335
|
+
}
|
|
264
336
|
await this.experienceStore.save(experience);
|
|
265
337
|
this.state.totalExperiences++;
|
|
266
338
|
this.audit('experience_stored', {
|
|
@@ -268,7 +340,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
268
340
|
outcome: experience.outcome,
|
|
269
341
|
lessons: experience.lessons,
|
|
270
342
|
});
|
|
271
|
-
this.
|
|
343
|
+
this.logger.info(` Experience: ${experience.outcome} — ${experience.lessons.length} lesson(s)`);
|
|
272
344
|
// Clear current state
|
|
273
345
|
this.state.currentObservation = null;
|
|
274
346
|
this.state.currentDecision = null;
|
|
@@ -294,6 +366,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
294
366
|
// ── Helpers ────────────────────────────────────────────────
|
|
295
367
|
setPhase(phase) {
|
|
296
368
|
this.state.phase = phase;
|
|
369
|
+
this.logger.setPhase(phase);
|
|
297
370
|
this.emit('phase', phase);
|
|
298
371
|
}
|
|
299
372
|
checkHourWindow() {
|
|
@@ -305,6 +378,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
305
378
|
trackTokens(tokens) {
|
|
306
379
|
this.hourWindow.tokens += tokens;
|
|
307
380
|
}
|
|
381
|
+
/** Estimate cost in cents from token usage (rough Sonnet pricing). */
|
|
382
|
+
trackCost(tokens) {
|
|
383
|
+
// Approximate: $3/MTok input, $15/MTok output, blended ~$6/MTok
|
|
384
|
+
const costPerToken = 6 / 1_000_000; // $6 per million tokens
|
|
385
|
+
this.sessionCostCents += tokens * costPerToken * 100;
|
|
386
|
+
}
|
|
308
387
|
recordError(phase, message, recoverable) {
|
|
309
388
|
const error = {
|
|
310
389
|
phase,
|
|
@@ -332,6 +411,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
332
411
|
planId: this.state.currentPlan?.id,
|
|
333
412
|
},
|
|
334
413
|
};
|
|
414
|
+
// Persist to hash-chained audit log (fire-and-forget)
|
|
415
|
+
this.auditLog.append(entry).catch((err) => {
|
|
416
|
+
this.logger.error(`Audit log write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
417
|
+
});
|
|
418
|
+
// Still emit for external listeners
|
|
335
419
|
this.emit('audit', entry);
|
|
336
420
|
}
|
|
337
421
|
describePayload(obs) {
|
|
@@ -349,10 +433,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
349
433
|
return `${p.url} (${p.statusCode})`;
|
|
350
434
|
}
|
|
351
435
|
}
|
|
352
|
-
log(message) {
|
|
353
|
-
const ts = new Date().toISOString().slice(11, 19);
|
|
354
|
-
console.log(`[${ts}] ${message}`);
|
|
355
|
-
}
|
|
356
436
|
sleep(ms) {
|
|
357
437
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
358
438
|
}
|
|
@@ -403,6 +483,7 @@ export function buildAgentConfig(targetPath, opts = {}) {
|
|
|
403
483
|
maxConcurrentSteps: 3,
|
|
404
484
|
maxPlanSteps: 10,
|
|
405
485
|
commandTimeoutMs: 30_000,
|
|
486
|
+
maxSessionCostCents: opts.maxSessionCostCents ?? 500,
|
|
406
487
|
},
|
|
407
488
|
signals,
|
|
408
489
|
approvalDefaults: {
|
|
@@ -414,6 +495,8 @@ export function buildAgentConfig(targetPath, opts = {}) {
|
|
|
414
495
|
},
|
|
415
496
|
modelId: opts.modelId ?? 'claude-sonnet-4-5-20250929',
|
|
416
497
|
experienceStorePath: opts.experiencesPath ?? `${rootDir}/.assay/experiences.jsonl`,
|
|
498
|
+
controlPort: opts.controlPort ?? 3700,
|
|
499
|
+
jsonLogs: opts.jsonLogs ?? false,
|
|
417
500
|
};
|
|
418
501
|
}
|
|
419
502
|
//# sourceMappingURL=agent-loop.js.map
|