@kuralle-agents/cli 0.13.1 → 0.15.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/chat.js +7 -7
- package/dist/fileStore.js +12 -2
- package/dist/send.js +55 -13
- package/dist/trace.js +2 -1
- package/package.json +10 -7
package/dist/chat.js
CHANGED
|
@@ -52,21 +52,21 @@ async function runTurn(demo, input, onText, onEvent) {
|
|
|
52
52
|
try {
|
|
53
53
|
for await (const part of handle.events) {
|
|
54
54
|
if (part.type === 'text-delta') {
|
|
55
|
-
text += part.delta;
|
|
55
|
+
text += part.payload.delta;
|
|
56
56
|
onText(text);
|
|
57
57
|
}
|
|
58
58
|
else if (part.type === 'tool-call')
|
|
59
|
-
onEvent(`⚙ tool ${part.toolName}`);
|
|
59
|
+
onEvent(`⚙ tool ${part.payload.toolName}`);
|
|
60
60
|
else if (part.type === 'flow-enter')
|
|
61
|
-
onEvent(`▸ enter flow ${part.flow}`);
|
|
61
|
+
onEvent(`▸ enter flow ${part.payload.flow}`);
|
|
62
62
|
else if (part.type === 'flow-end')
|
|
63
|
-
onEvent(`■ end flow ${part.flow}`);
|
|
63
|
+
onEvent(`■ end flow ${part.payload.flow}`);
|
|
64
64
|
else if (part.type === 'handoff')
|
|
65
|
-
onEvent(`→ handoff ${part.targetAgent}`);
|
|
65
|
+
onEvent(`→ handoff ${part.payload.targetAgent}`);
|
|
66
66
|
else if (part.type === 'paused')
|
|
67
|
-
onEvent(`⏸ paused ${part.waitingFor
|
|
67
|
+
onEvent(`⏸ paused ${part.payload.waitingFor}`);
|
|
68
68
|
else if (part.type === 'error')
|
|
69
|
-
onEvent(`✖ ${part.error}`);
|
|
69
|
+
onEvent(`✖ ${part.payload.error}`);
|
|
70
70
|
}
|
|
71
71
|
const res = await handle;
|
|
72
72
|
if (!text && typeof res.text === 'string')
|
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/send.js
CHANGED
|
@@ -2,9 +2,15 @@
|
|
|
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';
|
|
13
|
+
import { fileTraceStore } from './fileTraceStore.js';
|
|
8
14
|
function flag(argv, name) {
|
|
9
15
|
const i = argv.indexOf(name);
|
|
10
16
|
return i >= 0 ? argv[i + 1] : undefined;
|
|
@@ -14,13 +20,48 @@ export async function runSend(argv, buildRuntime) {
|
|
|
14
20
|
const storePath = flag(argv, '--store') ?? join(process.cwd(), 'runs/tui-sessions.json');
|
|
15
21
|
const doReset = argv.includes('--reset');
|
|
16
22
|
const doState = argv.includes('--state');
|
|
17
|
-
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
|
+
]);
|
|
18
54
|
const message = argv
|
|
19
|
-
.filter((a, i) => !a.startsWith('--') && !(i > 0 && (argv[i - 1]
|
|
55
|
+
.filter((a, i) => !a.startsWith('--') && !(i > 0 && consumesValue.has(argv[i - 1])))
|
|
20
56
|
.join(' ')
|
|
21
57
|
.trim();
|
|
22
58
|
const store = fileSessionStore(storePath);
|
|
23
|
-
|
|
59
|
+
// Traces must be file-backed too. `send` is one turn per process, so a MemoryTraceStore
|
|
60
|
+
// (what the loader defaults to whenever a session store is supplied) is discarded on
|
|
61
|
+
// exit and `kuralle trace --store` finds nothing — the exact sidecar `chat --store`
|
|
62
|
+
// writes and the CLI guide promises for both commands.
|
|
63
|
+
const traces = fileTraceStore(storePath.replace(/\.json$/, '') + '.traces.json');
|
|
64
|
+
const demo = buildRuntime(sessionId, store, traces);
|
|
24
65
|
async function readState() {
|
|
25
66
|
const s = await store.get(sessionId);
|
|
26
67
|
const rs = s?.durableRuns?.[sessionId]?.runState;
|
|
@@ -31,32 +72,33 @@ export async function runSend(argv, buildRuntime) {
|
|
|
31
72
|
console.log(`reset session "${sessionId}"`);
|
|
32
73
|
return;
|
|
33
74
|
}
|
|
34
|
-
|
|
75
|
+
// A decision can arrive with no message — approving is itself the turn.
|
|
76
|
+
if (doState || (!message && !signalDelivery)) {
|
|
35
77
|
console.log(await readState());
|
|
36
78
|
if (!message && !doState)
|
|
37
79
|
console.error('(no message — pass one to take a turn, or --state to inspect)');
|
|
38
80
|
return;
|
|
39
81
|
}
|
|
40
82
|
const events = [];
|
|
41
|
-
const handle = demo.runtime.run({ sessionId, input: message });
|
|
83
|
+
const handle = demo.runtime.run({ sessionId, input: message, ...(signalDelivery ? { signalDelivery } : {}) });
|
|
42
84
|
let text = '';
|
|
43
85
|
for await (const part of handle.events) {
|
|
44
86
|
if (part.type === 'text-delta') {
|
|
45
|
-
text += part.delta;
|
|
46
|
-
process.stdout.write(part.delta);
|
|
87
|
+
text += part.payload.delta;
|
|
88
|
+
process.stdout.write(part.payload.delta);
|
|
47
89
|
}
|
|
48
90
|
else if (part.type === 'tool-call')
|
|
49
|
-
events.push(`tool:${part.toolName}`);
|
|
91
|
+
events.push(`tool:${part.payload.toolName}`);
|
|
50
92
|
else if (part.type === 'flow-enter')
|
|
51
|
-
events.push(`enter:${part.flow}`);
|
|
93
|
+
events.push(`enter:${part.payload.flow}`);
|
|
52
94
|
else if (part.type === 'flow-end')
|
|
53
|
-
events.push(`end:${part.flow}`);
|
|
95
|
+
events.push(`end:${part.payload.flow}`);
|
|
54
96
|
else if (part.type === 'handoff')
|
|
55
|
-
events.push(`handoff:${part.targetAgent}`);
|
|
97
|
+
events.push(`handoff:${part.payload.targetAgent}`);
|
|
56
98
|
else if (part.type === 'paused')
|
|
57
|
-
events.push(`paused:${part.waitingFor
|
|
99
|
+
events.push(`paused:${part.payload.waitingFor}`);
|
|
58
100
|
else if (part.type === 'error')
|
|
59
|
-
events.push(`error:${part.error}`);
|
|
101
|
+
events.push(`error:${part.payload.error}`);
|
|
60
102
|
}
|
|
61
103
|
const res = await handle;
|
|
62
104
|
if (!text && typeof res.text === 'string')
|
package/dist/trace.js
CHANGED
|
@@ -4,7 +4,8 @@ import { fileSessionStore } from './fileStore.js';
|
|
|
4
4
|
import { fileTraceStore } from './fileTraceStore.js';
|
|
5
5
|
export async function runTrace(argv, buildRuntime) {
|
|
6
6
|
// --store <file> points at the same file `kuralle chat --store` / `kuralle send`
|
|
7
|
-
// persist to; traces live in the
|
|
7
|
+
// persist to; traces live in a sidecar with the extension replaced —
|
|
8
|
+
// `runs/app.json` -> `runs/app.traces.json` (JSONL, one span per line).
|
|
8
9
|
// Without wiring these, buildRuntime falls back to an in-memory store that is
|
|
9
10
|
// always empty in a fresh process, so no persisted trace is ever found.
|
|
10
11
|
const storeIdx = argv.indexOf('--store');
|
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.15.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.1",
|
|
24
|
+
"@kuralle-agents/core": "0.15.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.13.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.1"
|
|
35
36
|
},
|
|
36
37
|
"publishConfig": {
|
|
37
38
|
"access": "public"
|
|
@@ -44,6 +45,8 @@
|
|
|
44
45
|
"prebuild": "rm -rf dist",
|
|
45
46
|
"build": "tsc -p tsconfig.json",
|
|
46
47
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
47
|
-
"clean": "rm -rf dist"
|
|
48
|
+
"clean": "rm -rf dist",
|
|
49
|
+
"test": "bun test ./test",
|
|
50
|
+
"typecheck:examples": "tsc --noEmit -p tsconfig.examples.json"
|
|
48
51
|
}
|
|
49
52
|
}
|