@link-assistant/agent 0.24.0 → 0.24.1
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 +19 -0
- package/package.json +1 -1
- package/src/cli/continuous-mode.js +17 -112
- package/src/cli/event-handler.js +103 -82
- package/src/json-standard/index.ts +15 -2
package/README.md
CHANGED
|
@@ -286,6 +286,10 @@ Options:
|
|
|
286
286
|
Default: opencode/minimax-m2.5-free
|
|
287
287
|
--json-standard JSON output format standard
|
|
288
288
|
Choices: "opencode" (default), "claude" (experimental)
|
|
289
|
+
--output-format Alias for JSON output format
|
|
290
|
+
Choices: "json" (OpenCode), "stream-json" (Claude NDJSON)
|
|
291
|
+
--input-format Input format
|
|
292
|
+
Choices: "text" (default), "stream-json" (Claude JSONL)
|
|
289
293
|
--use-existing-claude-oauth Use existing Claude OAuth credentials
|
|
290
294
|
from ~/.claude/.credentials.json
|
|
291
295
|
--system-message Full override of the system message
|
|
@@ -390,6 +394,21 @@ echo "your message here" | agent
|
|
|
390
394
|
}
|
|
391
395
|
```
|
|
392
396
|
|
|
397
|
+
**Live `stream-json`:**
|
|
398
|
+
|
|
399
|
+
For bidirectional drivers, use:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
agent --input-format stream-json --output-format stream-json
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
The live contract supports `user_prompt`, `system`, `interrupt`, and
|
|
406
|
+
`permission_response` frames on stdin. It emits replay acknowledgements for
|
|
407
|
+
consumed user frames and an explicit idle/turn-boundary event
|
|
408
|
+
(`session_idle` in OpenCode output, `idle` in Claude output). See
|
|
409
|
+
[docs/stdin-mode.md](../docs/stdin-mode.md#live-stream-json-contract) for the
|
|
410
|
+
session resume and steering semantics.
|
|
411
|
+
|
|
393
412
|
## Supported Tools
|
|
394
413
|
|
|
395
414
|
All 13 tools are **enabled by default** with **no configuration required**. See [TOOLS.md](../TOOLS.md) for complete documentation.
|
package/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { Session } from '../session/index.ts';
|
|
|
10
10
|
import { SessionPrompt } from '../session/prompt.ts';
|
|
11
11
|
import { createEventHandler, serializeOutput } from '../json-standard/index.ts';
|
|
12
12
|
import { createContinuousStdinReader } from './input-queue.js';
|
|
13
|
+
import { outputBusEvent } from './event-handler.js';
|
|
13
14
|
import { Permission } from '../permission/index.ts';
|
|
14
15
|
import { Log } from '../util/log.ts';
|
|
15
16
|
import { config } from '../config/config.ts';
|
|
@@ -400,62 +401,14 @@ export async function runContinuousServerMode(
|
|
|
400
401
|
|
|
401
402
|
// Subscribe to all bus events and output in selected format
|
|
402
403
|
unsub = Bus.subscribeAll((event) => {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
type: 'step_start',
|
|
412
|
-
timestamp: Date.now(),
|
|
413
|
-
sessionID,
|
|
414
|
-
part,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
if (part.type === 'step-finish') {
|
|
419
|
-
eventHandler.output({
|
|
420
|
-
type: 'step_finish',
|
|
421
|
-
timestamp: Date.now(),
|
|
422
|
-
sessionID,
|
|
423
|
-
part,
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (part.type === 'text' && part.time?.end) {
|
|
428
|
-
eventHandler.output({
|
|
429
|
-
type: 'text',
|
|
430
|
-
timestamp: Date.now(),
|
|
431
|
-
sessionID,
|
|
432
|
-
part,
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (part.type === 'tool') {
|
|
437
|
-
eventHandler.output({
|
|
438
|
-
type: 'tool_use',
|
|
439
|
-
timestamp: Date.now(),
|
|
440
|
-
sessionID,
|
|
441
|
-
part,
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
if (event.type === 'session.error') {
|
|
447
|
-
const props = event.properties;
|
|
448
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
hasError = true;
|
|
452
|
-
eventHandler.output({
|
|
453
|
-
type: 'error',
|
|
454
|
-
timestamp: Date.now(),
|
|
455
|
-
sessionID,
|
|
456
|
-
error: props.error,
|
|
457
|
-
});
|
|
458
|
-
}
|
|
404
|
+
outputBusEvent({
|
|
405
|
+
event,
|
|
406
|
+
sessionID,
|
|
407
|
+
eventHandler,
|
|
408
|
+
onError: () => {
|
|
409
|
+
hasError = true;
|
|
410
|
+
},
|
|
411
|
+
});
|
|
459
412
|
});
|
|
460
413
|
|
|
461
414
|
// Create continuous stdin reader
|
|
@@ -661,62 +614,14 @@ export async function runContinuousDirectMode(
|
|
|
661
614
|
|
|
662
615
|
// Subscribe to all bus events and output in selected format
|
|
663
616
|
unsub = Bus.subscribeAll((event) => {
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
type: 'step_start',
|
|
673
|
-
timestamp: Date.now(),
|
|
674
|
-
sessionID,
|
|
675
|
-
part,
|
|
676
|
-
});
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
if (part.type === 'step-finish') {
|
|
680
|
-
eventHandler.output({
|
|
681
|
-
type: 'step_finish',
|
|
682
|
-
timestamp: Date.now(),
|
|
683
|
-
sessionID,
|
|
684
|
-
part,
|
|
685
|
-
});
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (part.type === 'text' && part.time?.end) {
|
|
689
|
-
eventHandler.output({
|
|
690
|
-
type: 'text',
|
|
691
|
-
timestamp: Date.now(),
|
|
692
|
-
sessionID,
|
|
693
|
-
part,
|
|
694
|
-
});
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (part.type === 'tool') {
|
|
698
|
-
eventHandler.output({
|
|
699
|
-
type: 'tool_use',
|
|
700
|
-
timestamp: Date.now(),
|
|
701
|
-
sessionID,
|
|
702
|
-
part,
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (event.type === 'session.error') {
|
|
708
|
-
const props = event.properties;
|
|
709
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
hasError = true;
|
|
713
|
-
eventHandler.output({
|
|
714
|
-
type: 'error',
|
|
715
|
-
timestamp: Date.now(),
|
|
716
|
-
sessionID,
|
|
717
|
-
error: props.error,
|
|
718
|
-
});
|
|
719
|
-
}
|
|
617
|
+
outputBusEvent({
|
|
618
|
+
event,
|
|
619
|
+
sessionID,
|
|
620
|
+
eventHandler,
|
|
621
|
+
onError: () => {
|
|
622
|
+
hasError = true;
|
|
623
|
+
},
|
|
624
|
+
});
|
|
720
625
|
});
|
|
721
626
|
|
|
722
627
|
// Create continuous stdin reader
|
package/src/cli/event-handler.js
CHANGED
|
@@ -26,105 +26,126 @@ export function createBusEventSubscription({
|
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
const unsub = Bus.subscribeAll((event) => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (part.type === 'step-start') {
|
|
38
|
-
eventHandler.output({
|
|
39
|
-
type: 'step_start',
|
|
40
|
-
timestamp: Date.now(),
|
|
41
|
-
sessionID,
|
|
42
|
-
part,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (part.type === 'step-finish') {
|
|
47
|
-
eventHandler.output({
|
|
48
|
-
type: 'step_finish',
|
|
49
|
-
timestamp: Date.now(),
|
|
50
|
-
sessionID,
|
|
51
|
-
part,
|
|
52
|
-
});
|
|
53
|
-
}
|
|
29
|
+
outputBusEvent({
|
|
30
|
+
event,
|
|
31
|
+
sessionID,
|
|
32
|
+
eventHandler,
|
|
33
|
+
onError,
|
|
34
|
+
onIdle: idleResolve,
|
|
35
|
+
});
|
|
36
|
+
});
|
|
54
37
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
type: 'text',
|
|
58
|
-
timestamp: Date.now(),
|
|
59
|
-
sessionID,
|
|
60
|
-
part,
|
|
61
|
-
});
|
|
62
|
-
}
|
|
38
|
+
return { unsub, idlePromise };
|
|
39
|
+
}
|
|
63
40
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
41
|
+
export function outputBusEvent({
|
|
42
|
+
event,
|
|
43
|
+
sessionID,
|
|
44
|
+
eventHandler,
|
|
45
|
+
onError,
|
|
46
|
+
onIdle,
|
|
47
|
+
}) {
|
|
48
|
+
// Output events in selected JSON format
|
|
49
|
+
if (event.type === 'message.part.updated') {
|
|
50
|
+
const part = event.properties.part;
|
|
51
|
+
if (part.sessionID !== sessionID) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
71
54
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
}
|
|
55
|
+
// Output different event types
|
|
56
|
+
if (part.type === 'step-start') {
|
|
57
|
+
eventHandler.output({
|
|
58
|
+
type: 'step_start',
|
|
59
|
+
timestamp: Date.now(),
|
|
60
|
+
sessionID,
|
|
61
|
+
part,
|
|
62
|
+
});
|
|
82
63
|
}
|
|
83
64
|
|
|
84
|
-
|
|
85
|
-
// The consumer replies with a `permission_response` frame over stdin which
|
|
86
|
-
// is routed to Permission.respond. No TUI is involved.
|
|
87
|
-
if (event.type === 'permission.updated') {
|
|
88
|
-
const permission = event.properties;
|
|
89
|
-
if (permission.sessionID !== sessionID) {
|
|
90
|
-
return;
|
|
91
|
-
}
|
|
65
|
+
if (part.type === 'step-finish') {
|
|
92
66
|
eventHandler.output({
|
|
93
|
-
type: '
|
|
67
|
+
type: 'step_finish',
|
|
94
68
|
timestamp: Date.now(),
|
|
95
69
|
sessionID,
|
|
96
|
-
|
|
97
|
-
callID: permission.callID,
|
|
98
|
-
tool: permission.type,
|
|
99
|
-
pattern: permission.pattern,
|
|
100
|
-
title: permission.title,
|
|
101
|
-
metadata: permission.metadata,
|
|
70
|
+
part,
|
|
102
71
|
});
|
|
103
72
|
}
|
|
104
73
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
74
|
+
if (part.type === 'text' && part.time?.end) {
|
|
75
|
+
eventHandler.output({
|
|
76
|
+
type: 'text',
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
sessionID,
|
|
79
|
+
part,
|
|
80
|
+
});
|
|
111
81
|
}
|
|
112
82
|
|
|
113
|
-
|
|
114
|
-
if (event.type === 'session.error') {
|
|
115
|
-
const props = event.properties;
|
|
116
|
-
if (props.sessionID !== sessionID || !props.error) {
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
onError();
|
|
83
|
+
if (part.type === 'tool') {
|
|
120
84
|
eventHandler.output({
|
|
121
|
-
type: '
|
|
85
|
+
type: 'tool_use',
|
|
122
86
|
timestamp: Date.now(),
|
|
123
87
|
sessionID,
|
|
124
|
-
|
|
88
|
+
part,
|
|
125
89
|
});
|
|
90
|
+
|
|
91
|
+
// If tool failed, also output an error event
|
|
92
|
+
if (part.state?.status === 'error') {
|
|
93
|
+
eventHandler.output({
|
|
94
|
+
type: 'error',
|
|
95
|
+
timestamp: Date.now(),
|
|
96
|
+
sessionID,
|
|
97
|
+
error: part.state.error || 'Tool execution failed',
|
|
98
|
+
});
|
|
99
|
+
}
|
|
126
100
|
}
|
|
127
|
-
}
|
|
101
|
+
}
|
|
128
102
|
|
|
129
|
-
|
|
103
|
+
// Emit a JSON permission request when a tool needs approval (issue #271).
|
|
104
|
+
// The consumer replies with a `permission_response` frame over stdin which
|
|
105
|
+
// is routed to Permission.respond. No TUI is involved.
|
|
106
|
+
if (event.type === 'permission.updated') {
|
|
107
|
+
const permission = event.properties;
|
|
108
|
+
if (permission.sessionID !== sessionID) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
eventHandler.output({
|
|
112
|
+
type: 'permission_request',
|
|
113
|
+
timestamp: Date.now(),
|
|
114
|
+
sessionID,
|
|
115
|
+
permissionID: permission.id,
|
|
116
|
+
callID: permission.callID,
|
|
117
|
+
tool: permission.type,
|
|
118
|
+
pattern: permission.pattern,
|
|
119
|
+
title: permission.title,
|
|
120
|
+
metadata: permission.metadata,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Handle session idle to know when to stop
|
|
125
|
+
if (
|
|
126
|
+
event.type === 'session.idle' &&
|
|
127
|
+
event.properties.sessionID === sessionID
|
|
128
|
+
) {
|
|
129
|
+
eventHandler.output({
|
|
130
|
+
type: 'session_idle',
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
sessionID,
|
|
133
|
+
});
|
|
134
|
+
onIdle?.();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Handle errors
|
|
138
|
+
if (event.type === 'session.error') {
|
|
139
|
+
const props = event.properties;
|
|
140
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
onError();
|
|
144
|
+
eventHandler.output({
|
|
145
|
+
type: 'error',
|
|
146
|
+
timestamp: Date.now(),
|
|
147
|
+
sessionID,
|
|
148
|
+
error: props.error,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
130
151
|
}
|
|
@@ -18,7 +18,13 @@ export type JsonStandard = 'opencode' | 'claude';
|
|
|
18
18
|
* OpenCode JSON event types
|
|
19
19
|
*/
|
|
20
20
|
export interface OpenCodeEvent {
|
|
21
|
-
type:
|
|
21
|
+
type:
|
|
22
|
+
| 'step_start'
|
|
23
|
+
| 'step_finish'
|
|
24
|
+
| 'text'
|
|
25
|
+
| 'tool_use'
|
|
26
|
+
| 'error'
|
|
27
|
+
| 'session_idle';
|
|
22
28
|
timestamp: number;
|
|
23
29
|
sessionID: string;
|
|
24
30
|
part?: Record<string, unknown>;
|
|
@@ -29,7 +35,7 @@ export interface OpenCodeEvent {
|
|
|
29
35
|
* Claude JSON event types (stream-json format)
|
|
30
36
|
*/
|
|
31
37
|
export interface ClaudeEvent {
|
|
32
|
-
type: 'init' | 'message' | 'tool_use' | 'tool_result' | 'result';
|
|
38
|
+
type: 'init' | 'message' | 'tool_use' | 'tool_result' | 'result' | 'idle';
|
|
33
39
|
timestamp?: string;
|
|
34
40
|
session_id?: string;
|
|
35
41
|
role?: 'assistant' | 'user';
|
|
@@ -126,6 +132,13 @@ export function convertOpenCodeToClaude(
|
|
|
126
132
|
duration_ms: event.timestamp - startTime,
|
|
127
133
|
};
|
|
128
134
|
|
|
135
|
+
case 'session_idle':
|
|
136
|
+
return {
|
|
137
|
+
type: 'idle',
|
|
138
|
+
timestamp,
|
|
139
|
+
session_id,
|
|
140
|
+
};
|
|
141
|
+
|
|
129
142
|
case 'error':
|
|
130
143
|
return {
|
|
131
144
|
type: 'result',
|