@kuralle-agents/cli 0.14.0 → 0.16.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 +7 -0
- package/dist/fileStore.js +12 -2
- package/dist/resume.d.ts +2 -0
- package/dist/resume.js +35 -0
- package/dist/send.js +40 -4
- package/package.json +9 -7
package/dist/cli.js
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
*
|
|
5
5
|
* kuralle chat [--trace] [--store <file>] [--session <id>] [--auto "msg1|msg2"] [--agent <path.ts>]
|
|
6
6
|
* kuralle send --session <id> [--store <file>] [--state|--reset] "<message>"
|
|
7
|
+
* kuralle resume <session> [--store <file>] [--summary <text>]
|
|
7
8
|
* kuralle sim --goal "<goal>" [--turns N] [--profile "<who>"] [--agent <path.ts>]
|
|
8
9
|
* kuralle trace <session> [--last] [--json] [--web] [--port N]
|
|
9
10
|
*/
|
|
10
11
|
import { resolveBuildRuntime } from './agentLoader.js';
|
|
11
12
|
import { runChat } from './chat.js';
|
|
13
|
+
import { runResume } from './resume.js';
|
|
12
14
|
import { runSend } from './send.js';
|
|
13
15
|
import { runSim } from './sim.js';
|
|
14
16
|
import { runTrace } from './trace.js';
|
|
@@ -17,6 +19,7 @@ const HELP = `kuralle — Kuralle agent CLI
|
|
|
17
19
|
Usage:
|
|
18
20
|
kuralle chat [--trace] [--store <file>] [--session <id>] [--auto "msg1|msg2"] [--agent <path.ts>]
|
|
19
21
|
kuralle send --session <id> [--store <file>] [--state|--reset] "<message>"
|
|
22
|
+
kuralle resume <session> [--store <file>] [--summary <text>]
|
|
20
23
|
kuralle sim --goal "<goal>" [--turns N] [--profile "<who>"] [--agent <path.ts>]
|
|
21
24
|
kuralle trace <session> [--last] [--json] [--web] [--port N]
|
|
22
25
|
|
|
@@ -27,6 +30,7 @@ Options:
|
|
|
27
30
|
--trace Live trace side panel — the built-in AgentTrace of each turn (chat only)
|
|
28
31
|
--store <file> Persist the session + traces to JSON files so chat survives across launches (chat only)
|
|
29
32
|
--session <id> Session id to resume with --store (default: "default")
|
|
33
|
+
--summary <text> Resolution note appended on resume (seen by the agent post-resume)
|
|
30
34
|
`;
|
|
31
35
|
function flag(argv, name) {
|
|
32
36
|
const i = argv.indexOf(name);
|
|
@@ -65,6 +69,9 @@ async function main() {
|
|
|
65
69
|
case 'send':
|
|
66
70
|
await runSend(subArgv, buildRuntime);
|
|
67
71
|
break;
|
|
72
|
+
case 'resume':
|
|
73
|
+
await runResume(subArgv, buildRuntime);
|
|
74
|
+
break;
|
|
68
75
|
case 'sim':
|
|
69
76
|
await runSim(subArgv, buildRuntime);
|
|
70
77
|
break;
|
package/dist/fileStore.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { dirname } from 'node:path';
|
|
9
|
-
import { reviveSession } from '@kuralle-agents/core';
|
|
9
|
+
import { reviveSession, StaleWriteError } from '@kuralle-agents/core';
|
|
10
10
|
export function fileSessionStore(path) {
|
|
11
11
|
const readAll = () => {
|
|
12
12
|
if (!existsSync(path))
|
|
@@ -28,8 +28,18 @@ export function fileSessionStore(path) {
|
|
|
28
28
|
return map[id] ? reviveSession(map[id]) : null;
|
|
29
29
|
},
|
|
30
30
|
async save(session) {
|
|
31
|
+
// Compare-and-swap, matching MemoryStore. The durable journal appends through
|
|
32
|
+
// `mutateSessionWithRetry`, which retries on StaleWriteError — a store that accepts
|
|
33
|
+
// a stale write silently drops the losing append instead, and the step it wrote is
|
|
34
|
+
// then missing when finalizeStep looks for it.
|
|
31
35
|
const map = readAll();
|
|
32
|
-
map[session.id]
|
|
36
|
+
const existing = map[session.id];
|
|
37
|
+
const expected = session.version ?? 0;
|
|
38
|
+
const stored = existing ? (existing.version ?? 0) : 0;
|
|
39
|
+
if (stored !== expected) {
|
|
40
|
+
throw new StaleWriteError(session.id, expected, stored);
|
|
41
|
+
}
|
|
42
|
+
map[session.id] = { ...session, updatedAt: new Date(), version: expected + 1 };
|
|
33
43
|
writeAll(map);
|
|
34
44
|
},
|
|
35
45
|
async delete(id) {
|
package/dist/resume.d.ts
ADDED
package/dist/resume.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* resume — hand a held-over session back to the bot after a human resolved an escalation.
|
|
3
|
+
*
|
|
4
|
+
* Usage: kuralle resume <session> [--store <path>] [--summary <text>]
|
|
5
|
+
*
|
|
6
|
+
* A terminal-handoff escalation parks the run (`status = 'paused'`, no `waitingFor`); every
|
|
7
|
+
* later turn is held with "a colleague is handling this" until this command runs.
|
|
8
|
+
* `runtime.resumeFromEscalation` appends the resolution note, clears the parked flow /
|
|
9
|
+
* escalation state, and marks the run runnable — the next `run()` continues with context.
|
|
10
|
+
*/
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { fileSessionStore } from './fileStore.js';
|
|
13
|
+
import { fileTraceStore } from './fileTraceStore.js';
|
|
14
|
+
function flag(argv, name) {
|
|
15
|
+
const i = argv.indexOf(name);
|
|
16
|
+
return i >= 0 ? argv[i + 1] : undefined;
|
|
17
|
+
}
|
|
18
|
+
export async function runResume(argv, buildRuntime) {
|
|
19
|
+
const storePath = flag(argv, '--store') ?? join(process.cwd(), 'runs/tui-sessions.json');
|
|
20
|
+
const summary = flag(argv, '--summary');
|
|
21
|
+
// Every flag whose VALUE must not be mistaken for the session id positional. Missing one
|
|
22
|
+
// here would let the value survive the filter and become the session id (see send.ts).
|
|
23
|
+
const consumesValue = new Set(['--store', '--summary']);
|
|
24
|
+
const positional = argv.filter((a, i) => !a.startsWith('--') && !(i > 0 && consumesValue.has(argv[i - 1])));
|
|
25
|
+
const sessionId = positional[0];
|
|
26
|
+
if (!sessionId) {
|
|
27
|
+
console.error('usage: kuralle resume <session> [--store <path>] [--summary <text>]');
|
|
28
|
+
process.exit(2);
|
|
29
|
+
}
|
|
30
|
+
const store = fileSessionStore(storePath);
|
|
31
|
+
const traces = fileTraceStore(storePath.replace(/\.json$/, '') + '.traces.json');
|
|
32
|
+
const demo = buildRuntime(sessionId, store, traces);
|
|
33
|
+
await demo.runtime.resumeFromEscalation(sessionId, summary !== undefined ? { resolutionSummary: summary } : undefined);
|
|
34
|
+
console.log(`resumed session "${sessionId}"${summary ? ` (${summary})` : ''}`);
|
|
35
|
+
}
|
package/dist/send.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* send — ONE turn against a PERSISTED session for adaptive multi-turn conversations.
|
|
3
3
|
*
|
|
4
4
|
* Flags: --session <id> · --store <file> · --state · --reset
|
|
5
|
+
* --approve [by] · --deny [by] · --signal <name> [--payload <json>]
|
|
6
|
+
*
|
|
7
|
+
* A `needsApproval` tool suspends the run durably. Without a way to deliver the decision the
|
|
8
|
+
* CLI could enter that pause and never leave it — every later turn re-requested approval and
|
|
9
|
+
* paused again. The runtime already accepted `signalDelivery`; only the CLI could not send one.
|
|
5
10
|
*/
|
|
6
11
|
import { join } from 'node:path';
|
|
7
12
|
import { fileSessionStore } from './fileStore.js';
|
|
@@ -15,9 +20,39 @@ export async function runSend(argv, buildRuntime) {
|
|
|
15
20
|
const storePath = flag(argv, '--store') ?? join(process.cwd(), 'runs/tui-sessions.json');
|
|
16
21
|
const doReset = argv.includes('--reset');
|
|
17
22
|
const doState = argv.includes('--state');
|
|
18
|
-
const
|
|
23
|
+
const approve = argv.includes('--approve');
|
|
24
|
+
const deny = argv.includes('--deny');
|
|
25
|
+
const signalName = flag(argv, '--signal');
|
|
26
|
+
const by = flag(argv, '--approve') ?? flag(argv, '--deny') ?? 'cli';
|
|
27
|
+
const signalDelivery = approve || deny
|
|
28
|
+
? {
|
|
29
|
+
// A fresh id per delivery: recordSignalDelivery dedupes on it, so reusing one
|
|
30
|
+
// would make a second decision a silent no-op.
|
|
31
|
+
signalId: `cli-${Date.now()}`,
|
|
32
|
+
name: '__approval',
|
|
33
|
+
payload: { approved: approve, by },
|
|
34
|
+
}
|
|
35
|
+
: signalName
|
|
36
|
+
? {
|
|
37
|
+
signalId: `cli-${Date.now()}`,
|
|
38
|
+
name: signalName,
|
|
39
|
+
payload: JSON.parse(flag(argv, '--payload') ?? '{}'),
|
|
40
|
+
}
|
|
41
|
+
: undefined;
|
|
42
|
+
// Every flag whose VALUE must not be mistaken for message text. Missing `--model` here
|
|
43
|
+
// meant the model id survived the filter and became the first word of every user turn —
|
|
44
|
+
// a whole live run was sent to the model with "gpt-4.1-mini " prepended to each message.
|
|
45
|
+
const consumesValue = new Set([
|
|
46
|
+
'--session',
|
|
47
|
+
'--store',
|
|
48
|
+
'--model',
|
|
49
|
+
'--signal',
|
|
50
|
+
'--payload',
|
|
51
|
+
'--approve',
|
|
52
|
+
'--deny',
|
|
53
|
+
]);
|
|
19
54
|
const message = argv
|
|
20
|
-
.filter((a, i) => !a.startsWith('--') && !(i > 0 && (argv[i - 1]
|
|
55
|
+
.filter((a, i) => !a.startsWith('--') && !(i > 0 && consumesValue.has(argv[i - 1])))
|
|
21
56
|
.join(' ')
|
|
22
57
|
.trim();
|
|
23
58
|
const store = fileSessionStore(storePath);
|
|
@@ -37,14 +72,15 @@ export async function runSend(argv, buildRuntime) {
|
|
|
37
72
|
console.log(`reset session "${sessionId}"`);
|
|
38
73
|
return;
|
|
39
74
|
}
|
|
40
|
-
|
|
75
|
+
// A decision can arrive with no message — approving is itself the turn.
|
|
76
|
+
if (doState || (!message && !signalDelivery)) {
|
|
41
77
|
console.log(await readState());
|
|
42
78
|
if (!message && !doState)
|
|
43
79
|
console.error('(no message — pass one to take a turn, or --state to inspect)');
|
|
44
80
|
return;
|
|
45
81
|
}
|
|
46
82
|
const events = [];
|
|
47
|
-
const handle = demo.runtime.run({ sessionId, input: message });
|
|
83
|
+
const handle = demo.runtime.run({ sessionId, input: message, ...(signalDelivery ? { signalDelivery } : {}) });
|
|
48
84
|
let text = '';
|
|
49
85
|
for await (const part of handle.events) {
|
|
50
86
|
if (part.type === 'text-delta') {
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"url": "git+https://github.com/kuralle/kuralle-agents.git",
|
|
7
7
|
"directory": "packages/cli"
|
|
8
8
|
},
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.16.0",
|
|
10
10
|
"description": "Kuralle CLI — interactive TUI chat, adaptive send, and conversation simulation",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"bin": {
|
|
@@ -20,18 +20,19 @@
|
|
|
20
20
|
"ink-text-input": "^6.0.0",
|
|
21
21
|
"react": "^19.2.7",
|
|
22
22
|
"zod": "^4.0.0",
|
|
23
|
-
"@kuralle-agents/
|
|
24
|
-
"@kuralle-agents/
|
|
23
|
+
"@kuralle-agents/trace-ui": "0.14.2",
|
|
24
|
+
"@kuralle-agents/core": "0.16.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
+
"@kuralle-agents/core": "0.x",
|
|
27
28
|
"ai": "^6.0.0",
|
|
28
|
-
"zod": "^4.0.0"
|
|
29
|
-
"@kuralle-agents/core": "0.14.0"
|
|
29
|
+
"zod": "^4.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^20.11.0",
|
|
33
33
|
"@types/react": "^19.2.17",
|
|
34
|
-
"typescript": "^5.3.0"
|
|
34
|
+
"typescript": "^5.3.0",
|
|
35
|
+
"@kuralle-agents/fs": "0.14.2"
|
|
35
36
|
},
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"build": "tsc -p tsconfig.json",
|
|
46
47
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
47
48
|
"clean": "rm -rf dist",
|
|
48
|
-
"test": "bun test ./test"
|
|
49
|
+
"test": "bun test ./test",
|
|
50
|
+
"typecheck:examples": "tsc --noEmit -p tsconfig.examples.json"
|
|
49
51
|
}
|
|
50
52
|
}
|