nexus-agentd 0.1.9 → 0.2.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 +18 -4
- package/dist/a2a/runtime.d.ts +3 -0
- package/dist/a2a/runtime.js +72 -3
- package/dist/acp/runtime.js +21 -10
- package/dist/completion-contract.d.ts +1 -0
- package/dist/completion-contract.js +9 -0
- package/dist/run-store.d.ts +1 -1
- package/dist/session-contract.d.ts +2 -1
- package/dist/session.d.ts +3 -1
- package/dist/session.js +52 -1
- package/dist/types.d.ts +16 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,9 +62,22 @@ Workspace 配置保持不变。
|
|
|
62
62
|
默认值分别是 24 小时、30 分钟和 60 秒;A2A 请求超时在每个 Agent 的编辑页单独设置,默认 60 秒、
|
|
63
63
|
最大 30 分钟。
|
|
64
64
|
|
|
65
|
-
## Agent 协议
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
## Agent 协议
|
|
66
|
+
|
|
67
|
+
每个任务都会附带协议级完成约束:Agent 在结束 turn 前必须处理完工作,等待用户输入或授权时必须使用
|
|
68
|
+
协议请求,并提供非空最终说明或 Artifact。Gateway 只接受以下完成证明:
|
|
69
|
+
|
|
70
|
+
- ACP `session/prompt` 返回 `stopReason=end_turn`;`max_tokens`、`max_turn_requests`、拒绝和取消不会被
|
|
71
|
+
误报为成功。
|
|
72
|
+
- A2A Task 明确进入 `COMPLETED`;如果远端直接返回 Message 而没有创建 Task,则以完整消息流结束作为
|
|
73
|
+
turn 边界。已经出现 Task 状态但没有终态的流会标记失败。
|
|
74
|
+
- Session 仍有待处理的 permission/input 请求,或者最终文本与 Artifact 都为空时,不允许进入
|
|
75
|
+
`completed`。
|
|
76
|
+
|
|
77
|
+
成功的 Session 响应包含 `completion`,其中记录当前 Run ID、协议、完成来源、stop reason、最终文本和
|
|
78
|
+
产物存在性以及完成时间。这个证明验证的是协议边界和结果存在性,不替代业务内容本身的语义验收。
|
|
79
|
+
|
|
80
|
+
### ACP
|
|
68
81
|
|
|
69
82
|
| Driver | 默认入口 |
|
|
70
83
|
|---|---|
|
|
@@ -199,7 +212,8 @@ POST /v1/sessions/:id/cancel
|
|
|
199
212
|
GET /v1/sessions/:id/events
|
|
200
213
|
```
|
|
201
214
|
|
|
202
|
-
`/v1/meta` 和 Session 响应包含 Gateway `instanceId
|
|
215
|
+
`/v1/meta` 和 Session 响应包含 Gateway `instanceId`;完成的 Session 还包含与当前 Run 绑定的
|
|
216
|
+
`completion` 证明,客户端可识别进程重启和迟到/伪造的完成状态。授权与输入通过精确的
|
|
203
217
|
`requestId` 解析;过期 ID 返回 `409`,不会误答后续请求。`DELETE /v1/sessions/:id` 会取消活动任务、
|
|
204
218
|
释放 Agent 进程并移除内存 Session。
|
|
205
219
|
|
package/dist/a2a/runtime.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export declare class A2AClientRuntime implements AgentSessionRuntime {
|
|
|
14
14
|
private activeController?;
|
|
15
15
|
private readonly artifactCache;
|
|
16
16
|
private readonly emittedMessages;
|
|
17
|
+
private sawTaskStatus;
|
|
18
|
+
private sawAgentMessage;
|
|
19
|
+
private completionProof?;
|
|
17
20
|
constructor(config: AgentdA2AConfig, sink: AgentSessionSink, promptTimeoutMs: number);
|
|
18
21
|
start(): Promise<void>;
|
|
19
22
|
prompt(message: string, attachments?: AgentdInputAttachment[]): Promise<void>;
|
package/dist/a2a/runtime.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { Role, TaskState } from '@a2a-js/sdk';
|
|
3
3
|
import { ClientFactory, DefaultAgentCardResolver, JsonRpcTransportFactory, RestTransportFactory } from '@a2a-js/sdk/client';
|
|
4
|
+
import { TURN_COMPLETION_CONTRACT } from '../completion-contract.js';
|
|
4
5
|
export async function probeA2AAgent(id, config) {
|
|
5
6
|
const startedAt = Date.now();
|
|
6
7
|
if (config.enabled === false)
|
|
@@ -47,6 +48,9 @@ export class A2AClientRuntime {
|
|
|
47
48
|
activeController;
|
|
48
49
|
artifactCache = new Map();
|
|
49
50
|
emittedMessages = new Set();
|
|
51
|
+
sawTaskStatus = false;
|
|
52
|
+
sawAgentMessage = false;
|
|
53
|
+
completionProof;
|
|
50
54
|
constructor(config, sink, promptTimeoutMs) {
|
|
51
55
|
this.config = config;
|
|
52
56
|
this.sink = sink;
|
|
@@ -76,6 +80,9 @@ export class A2AClientRuntime {
|
|
|
76
80
|
if (this.disposed)
|
|
77
81
|
throw new Error('A2A runtime is disposed');
|
|
78
82
|
this.prompting = true;
|
|
83
|
+
this.sawTaskStatus = false;
|
|
84
|
+
this.sawAgentMessage = false;
|
|
85
|
+
this.completionProof = undefined;
|
|
79
86
|
this.sink.clearPending();
|
|
80
87
|
this.sink.setState('running');
|
|
81
88
|
const controller = new AbortController();
|
|
@@ -89,8 +96,22 @@ export class A2AClientRuntime {
|
|
|
89
96
|
})) {
|
|
90
97
|
this.processResponse(response);
|
|
91
98
|
}
|
|
92
|
-
if (this.sink.state === 'running')
|
|
93
|
-
this.
|
|
99
|
+
if (this.sink.state === 'running') {
|
|
100
|
+
if (this.completionProof) {
|
|
101
|
+
this.sink.completeTurn(this.completionProof);
|
|
102
|
+
}
|
|
103
|
+
else if (!this.sawTaskStatus && this.sawAgentMessage) {
|
|
104
|
+
this.sink.completeTurn({
|
|
105
|
+
source: 'a2a_message_stream',
|
|
106
|
+
stopReason: 'message_stream_end'
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
this.sink.setState('failed', this.sawTaskStatus
|
|
111
|
+
? 'A2A stream ended without a terminal task status.'
|
|
112
|
+
: 'A2A stream ended without an agent result.');
|
|
113
|
+
}
|
|
114
|
+
}
|
|
94
115
|
}
|
|
95
116
|
catch (error) {
|
|
96
117
|
if (this.sink.state !== 'canceled') {
|
|
@@ -156,6 +177,12 @@ export class A2AClientRuntime {
|
|
|
156
177
|
filename: '',
|
|
157
178
|
mediaType: 'text/plain'
|
|
158
179
|
},
|
|
180
|
+
{
|
|
181
|
+
content: { $case: 'text', value: TURN_COMPLETION_CONTRACT },
|
|
182
|
+
metadata: undefined,
|
|
183
|
+
filename: '',
|
|
184
|
+
mediaType: 'text/plain'
|
|
185
|
+
},
|
|
159
186
|
...attachments.map((attachment) => ({
|
|
160
187
|
content: {
|
|
161
188
|
$case: 'raw',
|
|
@@ -210,6 +237,7 @@ export class A2AClientRuntime {
|
|
|
210
237
|
processMessage(message) {
|
|
211
238
|
if (!message || message.role !== Role.ROLE_AGENT)
|
|
212
239
|
return;
|
|
240
|
+
this.sawAgentMessage = true;
|
|
213
241
|
this.captureIds(message.taskId, message.contextId);
|
|
214
242
|
if (message.messageId && this.emittedMessages.has(message.messageId))
|
|
215
243
|
return;
|
|
@@ -222,10 +250,18 @@ export class A2AClientRuntime {
|
|
|
222
250
|
content: { type: 'text', text }
|
|
223
251
|
});
|
|
224
252
|
}
|
|
253
|
+
message.parts.forEach((part, index) => {
|
|
254
|
+
const artifact = artifactFromPart(part, `message:${message.messageId || 'agent'}:${index}`);
|
|
255
|
+
if (!artifact)
|
|
256
|
+
return;
|
|
257
|
+
this.sink.addArtifact(artifact);
|
|
258
|
+
this.sink.emit('artifact', artifact);
|
|
259
|
+
});
|
|
225
260
|
}
|
|
226
261
|
processStatus(status) {
|
|
227
262
|
if (!status)
|
|
228
263
|
return;
|
|
264
|
+
this.sawTaskStatus = true;
|
|
229
265
|
this.processMessage(status.message);
|
|
230
266
|
const statusText = textParts(status.message?.parts || []).join('\n').trim();
|
|
231
267
|
switch (status.state) {
|
|
@@ -235,7 +271,10 @@ export class A2AClientRuntime {
|
|
|
235
271
|
break;
|
|
236
272
|
case TaskState.TASK_STATE_COMPLETED:
|
|
237
273
|
this.sink.clearPending();
|
|
238
|
-
this.
|
|
274
|
+
this.completionProof = {
|
|
275
|
+
source: 'a2a_task_status',
|
|
276
|
+
stopReason: 'completed'
|
|
277
|
+
};
|
|
239
278
|
break;
|
|
240
279
|
case TaskState.TASK_STATE_CANCELED:
|
|
241
280
|
this.sink.clearPending();
|
|
@@ -349,6 +388,36 @@ function artifactFromA2A(artifact) {
|
|
|
349
388
|
metadata: artifact.metadata || undefined
|
|
350
389
|
};
|
|
351
390
|
}
|
|
391
|
+
function artifactFromPart(part, id) {
|
|
392
|
+
switch (part.content?.$case) {
|
|
393
|
+
case 'raw':
|
|
394
|
+
return {
|
|
395
|
+
id,
|
|
396
|
+
name: part.filename || id,
|
|
397
|
+
filename: part.filename || undefined,
|
|
398
|
+
mediaType: part.mediaType || undefined,
|
|
399
|
+
bytesBase64: Buffer.from(part.content.value).toString('base64')
|
|
400
|
+
};
|
|
401
|
+
case 'url':
|
|
402
|
+
return {
|
|
403
|
+
id,
|
|
404
|
+
name: part.filename || part.content.value || id,
|
|
405
|
+
filename: part.filename || undefined,
|
|
406
|
+
mediaType: part.mediaType || undefined,
|
|
407
|
+
url: part.content.value || undefined
|
|
408
|
+
};
|
|
409
|
+
case 'data':
|
|
410
|
+
return {
|
|
411
|
+
id,
|
|
412
|
+
name: part.filename || id,
|
|
413
|
+
filename: part.filename || undefined,
|
|
414
|
+
mediaType: part.mediaType || undefined,
|
|
415
|
+
data: part.content.value
|
|
416
|
+
};
|
|
417
|
+
default:
|
|
418
|
+
return undefined;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
352
421
|
function textParts(parts) {
|
|
353
422
|
return parts.flatMap((part) => part.content?.$case === 'text' && part.content.value ? [part.content.value] : []);
|
|
354
423
|
}
|
package/dist/acp/runtime.js
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { Readable, Writable } from 'node:stream';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import * as acp from '@agentclientprotocol/sdk';
|
|
7
|
+
import { TURN_COMPLETION_CONTRACT } from '../completion-contract.js';
|
|
7
8
|
export class AcpProcessRuntime {
|
|
8
9
|
driver;
|
|
9
10
|
sink;
|
|
@@ -60,7 +61,7 @@ export class AcpProcessRuntime {
|
|
|
60
61
|
},
|
|
61
62
|
clientInfo: {
|
|
62
63
|
name: 'nexus-agentd',
|
|
63
|
-
version: '0.
|
|
64
|
+
version: '0.2.0'
|
|
64
65
|
}
|
|
65
66
|
});
|
|
66
67
|
if (initialize.protocolVersion !== acp.PROTOCOL_VERSION) {
|
|
@@ -94,14 +95,21 @@ export class AcpProcessRuntime {
|
|
|
94
95
|
if (this.sink.state === 'canceled' || this.sink.state === 'failed') {
|
|
95
96
|
return;
|
|
96
97
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
98
|
+
switch (response.stopReason) {
|
|
99
|
+
case 'end_turn':
|
|
100
|
+
this.sink.completeTurn({
|
|
101
|
+
source: 'acp_prompt_response',
|
|
102
|
+
stopReason: response.stopReason
|
|
103
|
+
});
|
|
104
|
+
break;
|
|
105
|
+
case 'cancelled':
|
|
106
|
+
this.sink.setState('canceled');
|
|
107
|
+
break;
|
|
108
|
+
case 'refusal':
|
|
109
|
+
this.sink.setState('failed', 'ACP agent refused the prompt');
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
this.sink.setState('failed', `ACP turn stopped before completion: ${response.stopReason}`);
|
|
105
113
|
}
|
|
106
114
|
}
|
|
107
115
|
catch (error) {
|
|
@@ -440,7 +448,10 @@ export class AcpProcessRuntime {
|
|
|
440
448
|
return value;
|
|
441
449
|
}
|
|
442
450
|
async promptBlocks(message, attachments) {
|
|
443
|
-
const blocks = [
|
|
451
|
+
const blocks = [
|
|
452
|
+
{ type: 'text', text: message },
|
|
453
|
+
{ type: 'text', text: TURN_COMPLETION_CONTRACT }
|
|
454
|
+
];
|
|
444
455
|
for (const attachment of attachments) {
|
|
445
456
|
const mediaType = attachment.mediaType || 'application/octet-stream';
|
|
446
457
|
const data = attachment.bytes.toString('base64');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TURN_COMPLETION_CONTRACT: string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const TURN_COMPLETION_CONTRACT = [
|
|
2
|
+
'<agent_nexus_completion_contract>',
|
|
3
|
+
'Do not end this turn while requested work is still running or incomplete.',
|
|
4
|
+
'If you need user input or permission, use the protocol input/permission request and wait for the response.',
|
|
5
|
+
'Before ending the turn, verify the requested work and provide a non-empty final response.',
|
|
6
|
+
'The final response must summarize the outcome, verification performed, and workspace-relative paths for generated files.',
|
|
7
|
+
'Attach generated resources or artifacts through the protocol when supported.',
|
|
8
|
+
'</agent_nexus_completion_contract>'
|
|
9
|
+
].join('\n');
|
package/dist/run-store.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export declare class RunStore {
|
|
|
32
32
|
constructor(filePath: string, maxRuns?: number, maxTaskChars?: number);
|
|
33
33
|
init(): Promise<void>;
|
|
34
34
|
create(input: CreateRunInput): AgentdRunDetail;
|
|
35
|
-
update(id: string, patch: Partial<Pick<StoredRun, 'protocolSessionId' | 'state' | 'progress' | 'output' | 'error' | 'artifacts' | 'artifactCount' | 'updatedAt' | 'endedAt' | 'durationMs' | 'inputAttachmentCount'>>): AgentdRunDetail | undefined;
|
|
35
|
+
update(id: string, patch: Partial<Pick<StoredRun, 'protocolSessionId' | 'state' | 'progress' | 'output' | 'error' | 'artifacts' | 'artifactCount' | 'updatedAt' | 'endedAt' | 'durationMs' | 'inputAttachmentCount' | 'completion'>>): AgentdRunDetail | undefined;
|
|
36
36
|
get(id: string): AgentdRunDetail | undefined;
|
|
37
37
|
list(query?: RunListQuery): {
|
|
38
38
|
runs: AgentdRunView[];
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { AgentdArtifact, AgentdEventType, AgentdInputAttachment, AgentdPendingRequest, AgentdPendingResponse, AgentdSessionState } from './types.js';
|
|
1
|
+
import type { AgentdArtifact, AgentdEventType, AgentdInputAttachment, AgentdPendingRequest, AgentdPendingResponse, AgentdSessionState, AgentdTurnCompletionProof } from './types.js';
|
|
2
2
|
export interface AgentSessionSink {
|
|
3
3
|
readonly id: string;
|
|
4
4
|
readonly state: AgentdSessionState;
|
|
5
5
|
readonly protocolSessionId?: string;
|
|
6
6
|
setProtocolSessionId(id: string): void;
|
|
7
7
|
setState(state: AgentdSessionState, error?: string): void;
|
|
8
|
+
completeTurn(proof: AgentdTurnCompletionProof): boolean;
|
|
8
9
|
appendOutput(text: string): void;
|
|
9
10
|
addArtifact(artifact: AgentdArtifact): void;
|
|
10
11
|
setPending(request: AgentdPendingRequest): void;
|
package/dist/session.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { AgentDriver } from './drivers/index.js';
|
|
|
2
2
|
import { SessionEventLog } from './events.js';
|
|
3
3
|
import { RunStore, type RunListQuery } from './run-store.js';
|
|
4
4
|
import type { AcpSessionSink, AgentSessionRuntime, AgentSessionSink } from './session-contract.js';
|
|
5
|
-
import type { AgentdAgentView, AgentdArtifact, AgentdConfig, AgentdEvent, AgentdEventType, AgentdInputAttachmentView, AgentdPendingRequest, AgentdPendingResponse, AgentdProtocol, AgentdSessionState, AgentdSessionView } from './types.js';
|
|
5
|
+
import type { AgentdAgentView, AgentdArtifact, AgentdConfig, AgentdEvent, AgentdEventType, AgentdInputAttachmentView, AgentdPendingRequest, AgentdPendingResponse, AgentdProtocol, AgentdSessionState, AgentdSessionView, AgentdTurnCompletionProof } from './types.js';
|
|
6
6
|
import type { WorkspacePolicy } from './workspace.js';
|
|
7
7
|
export declare class ManagedSession implements AgentSessionSink, AcpSessionSink {
|
|
8
8
|
readonly agentId: string;
|
|
@@ -23,6 +23,7 @@ export declare class ManagedSession implements AgentSessionSink, AcpSessionSink
|
|
|
23
23
|
output: string;
|
|
24
24
|
error?: string;
|
|
25
25
|
pendingRequest?: AgentdPendingRequest;
|
|
26
|
+
private completion?;
|
|
26
27
|
private artifacts;
|
|
27
28
|
private inputAttachments;
|
|
28
29
|
private runtime?;
|
|
@@ -31,6 +32,7 @@ export declare class ManagedSession implements AgentSessionSink, AcpSessionSink
|
|
|
31
32
|
setProtocolSessionId(id: string): void;
|
|
32
33
|
setAcpSessionId(id: string): void;
|
|
33
34
|
setState(state: AgentdSessionState, error?: string): void;
|
|
35
|
+
completeTurn(proof: AgentdTurnCompletionProof): boolean;
|
|
34
36
|
appendOutput(text: string): void;
|
|
35
37
|
addArtifact(artifact: AgentdArtifact): void;
|
|
36
38
|
publishFile(filePath: string): Promise<AgentdArtifact>;
|
package/dist/session.js
CHANGED
|
@@ -31,6 +31,7 @@ export class ManagedSession {
|
|
|
31
31
|
output = '';
|
|
32
32
|
error;
|
|
33
33
|
pendingRequest;
|
|
34
|
+
completion;
|
|
34
35
|
artifacts = [];
|
|
35
36
|
inputAttachments = new Map();
|
|
36
37
|
runtime;
|
|
@@ -61,6 +62,8 @@ export class ManagedSession {
|
|
|
61
62
|
setState(state, error) {
|
|
62
63
|
if (this.state === state && this.error === error)
|
|
63
64
|
return;
|
|
65
|
+
if (state !== 'completed')
|
|
66
|
+
this.completion = undefined;
|
|
64
67
|
this.state = state;
|
|
65
68
|
this.error = error;
|
|
66
69
|
this.updatedAt = Date.now();
|
|
@@ -71,15 +74,52 @@ export class ManagedSession {
|
|
|
71
74
|
: state === 'canceled'
|
|
72
75
|
? 'canceled'
|
|
73
76
|
: 'session_state';
|
|
74
|
-
this.events.append(type, {
|
|
77
|
+
this.events.append(type, {
|
|
78
|
+
state,
|
|
79
|
+
...(error ? { error } : {}),
|
|
80
|
+
...(state === 'completed' && this.completion
|
|
81
|
+
? { completion: structuredClone(this.completion) }
|
|
82
|
+
: {})
|
|
83
|
+
});
|
|
75
84
|
const endedAt = isTerminal(state) ? this.updatedAt : undefined;
|
|
76
85
|
this.syncRun({
|
|
77
86
|
state,
|
|
78
87
|
error,
|
|
79
88
|
progress: progressForState(state, error),
|
|
89
|
+
completion: state === 'completed' && this.completion
|
|
90
|
+
? structuredClone(this.completion)
|
|
91
|
+
: undefined,
|
|
80
92
|
...(endedAt ? { endedAt } : {})
|
|
81
93
|
});
|
|
82
94
|
}
|
|
95
|
+
completeTurn(proof) {
|
|
96
|
+
if (this.pendingRequest || isInteractive(this.state)) {
|
|
97
|
+
this.emit('terminal_output', {
|
|
98
|
+
stream: 'system',
|
|
99
|
+
text: `Ignored ${proof.source} completion while the session is waiting for ${this.pendingRequest?.kind || 'input'}.`
|
|
100
|
+
});
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
if (this.state !== 'running')
|
|
104
|
+
return false;
|
|
105
|
+
const outputPresent = this.output.trim().length > 0;
|
|
106
|
+
const artifactCount = this.artifacts.length;
|
|
107
|
+
if (!outputPresent && artifactCount < 1) {
|
|
108
|
+
this.setState('failed', `Agent ended the turn (${proof.stopReason}) without a final response or artifact.`);
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
this.completion = {
|
|
112
|
+
...structuredClone(proof),
|
|
113
|
+
runId: this.currentRunId,
|
|
114
|
+
protocol: this.protocol,
|
|
115
|
+
verified: true,
|
|
116
|
+
outputPresent,
|
|
117
|
+
artifactCount,
|
|
118
|
+
completedAt: Date.now()
|
|
119
|
+
};
|
|
120
|
+
this.setState('completed');
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
83
123
|
appendOutput(text) {
|
|
84
124
|
if (!text)
|
|
85
125
|
return;
|
|
@@ -186,6 +226,7 @@ export class ManagedSession {
|
|
|
186
226
|
}
|
|
187
227
|
}
|
|
188
228
|
setPending(request) {
|
|
229
|
+
this.completion = undefined;
|
|
189
230
|
this.pendingRequest = structuredClone(request);
|
|
190
231
|
this.state = request.kind === 'permission' ? 'permission_required' : 'input_required';
|
|
191
232
|
this.updatedAt = Date.now();
|
|
@@ -247,6 +288,7 @@ export class ManagedSession {
|
|
|
247
288
|
}
|
|
248
289
|
this.output = '';
|
|
249
290
|
this.artifacts = [];
|
|
291
|
+
this.completion = undefined;
|
|
250
292
|
this.error = undefined;
|
|
251
293
|
this.updatedAt = Date.now();
|
|
252
294
|
this.currentRunId = this.runStore?.create({
|
|
@@ -307,6 +349,9 @@ export class ManagedSession {
|
|
|
307
349
|
pendingRequest: this.pendingRequest
|
|
308
350
|
? structuredClone(this.pendingRequest)
|
|
309
351
|
: undefined,
|
|
352
|
+
completion: this.completion
|
|
353
|
+
? structuredClone(this.completion)
|
|
354
|
+
: undefined,
|
|
310
355
|
lastEventId: this.events.lastId,
|
|
311
356
|
createdAt: this.createdAt,
|
|
312
357
|
updatedAt: this.updatedAt
|
|
@@ -326,6 +371,9 @@ export class ManagedSession {
|
|
|
326
371
|
output: this.output || undefined,
|
|
327
372
|
artifacts: this.artifacts.map(runArtifact),
|
|
328
373
|
artifactCount: this.artifacts.length,
|
|
374
|
+
completion: this.completion
|
|
375
|
+
? structuredClone(this.completion)
|
|
376
|
+
: undefined,
|
|
329
377
|
updatedAt: this.updatedAt,
|
|
330
378
|
...patch
|
|
331
379
|
});
|
|
@@ -728,6 +776,9 @@ function isTerminal(state) {
|
|
|
728
776
|
function isActive(state) {
|
|
729
777
|
return state === 'running' || state === 'input_required' || state === 'permission_required';
|
|
730
778
|
}
|
|
779
|
+
function isInteractive(state) {
|
|
780
|
+
return state === 'input_required' || state === 'permission_required';
|
|
781
|
+
}
|
|
731
782
|
function errorMessage(error) {
|
|
732
783
|
return error instanceof Error ? error.message : String(error);
|
|
733
784
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
export type AgentdSessionState = 'created' | 'running' | 'input_required' | 'permission_required' | 'completed' | 'failed' | 'canceled';
|
|
2
2
|
export type AgentdProtocol = 'acp' | 'a2a';
|
|
3
3
|
export type PermissionPolicy = 'ask' | 'deny';
|
|
4
|
+
export type AgentdTurnCompletionSource = 'acp_prompt_response' | 'a2a_task_status' | 'a2a_message_stream';
|
|
5
|
+
export interface AgentdTurnCompletionProof {
|
|
6
|
+
source: AgentdTurnCompletionSource;
|
|
7
|
+
stopReason: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AgentdTurnCompletion extends AgentdTurnCompletionProof {
|
|
10
|
+
runId?: string;
|
|
11
|
+
protocol: AgentdProtocol;
|
|
12
|
+
verified: true;
|
|
13
|
+
outputPresent: boolean;
|
|
14
|
+
artifactCount: number;
|
|
15
|
+
completedAt: number;
|
|
16
|
+
}
|
|
4
17
|
export declare const agentdDriverKinds: readonly ["opencode", "claude", "codex", "pi", "openclaw", "hermes"];
|
|
5
18
|
export type AgentdDriverKind = (typeof agentdDriverKinds)[number];
|
|
6
19
|
export interface AgentdDriverConfig {
|
|
@@ -189,6 +202,8 @@ export interface AgentdSessionView {
|
|
|
189
202
|
artifacts: AgentdArtifact[];
|
|
190
203
|
inputAttachments?: AgentdInputAttachmentView[];
|
|
191
204
|
pendingRequest?: AgentdPendingRequest;
|
|
205
|
+
/** Structural proof that the current protocol turn reached a valid terminal boundary. */
|
|
206
|
+
completion?: AgentdTurnCompletion;
|
|
192
207
|
lastEventId?: string;
|
|
193
208
|
createdAt: number;
|
|
194
209
|
updatedAt: number;
|
|
@@ -223,6 +238,7 @@ export interface AgentdRunView {
|
|
|
223
238
|
error?: string;
|
|
224
239
|
artifactCount: number;
|
|
225
240
|
inputAttachmentCount?: number;
|
|
241
|
+
completion?: AgentdTurnCompletion;
|
|
226
242
|
startedAt: number;
|
|
227
243
|
updatedAt: number;
|
|
228
244
|
endedAt?: number;
|