@adecore/agents 0.35.5 → 0.37.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.
|
@@ -473,8 +473,11 @@ export class CodexBackend {
|
|
|
473
473
|
}
|
|
474
474
|
const turnId = this.protocol.turnId;
|
|
475
475
|
if (!turnId) {
|
|
476
|
-
//
|
|
477
|
-
|
|
476
|
+
// An optional question can outlive its turn; its answer resumes the same chat as plain text.
|
|
477
|
+
this.turnUnnamed = true;
|
|
478
|
+
this.promptId = null;
|
|
479
|
+
this.request('turn/start', this.turnStartParams(textInput(answer.text)));
|
|
480
|
+
return true;
|
|
478
481
|
}
|
|
479
482
|
this.request('turn/steer', { threadId: this.threadId, expectedTurnId: turnId, input: textInput(answer.text) });
|
|
480
483
|
return true;
|
|
@@ -484,6 +487,9 @@ export class CodexBackend {
|
|
|
484
487
|
return answer?.kind === 'respond' ? this.respond(requestId, answer.rpcId, answer.result) : this.protocol.dismissQuestion(requestId);
|
|
485
488
|
}
|
|
486
489
|
declineRequest(requestId, _message) {
|
|
490
|
+
if (this.protocol.dismissQuestion(requestId)) {
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
487
493
|
const answer = this.protocol.declineRequest(requestId);
|
|
488
494
|
if (!this.transport || !answer) {
|
|
489
495
|
return false;
|
|
@@ -237,7 +237,7 @@ export class CodexProtocol {
|
|
|
237
237
|
const threadId = frame.method === 'thread/started' ? (isRecord(params.thread) ? str(params.thread.id) : null) : str(params.threadId);
|
|
238
238
|
if (threadId !== null && this.ownThreadId !== null && threadId !== this.ownThreadId) {
|
|
239
239
|
if (frame.method === 'turn/completed') {
|
|
240
|
-
this.withdrawOwner(threadId, isRecord(params.turn) ? str(params.turn.id) : null, events);
|
|
240
|
+
this.withdrawOwner(threadId, isRecord(params.turn) ? str(params.turn.id) : null, events, isRecord(params.turn) && params.turn.status === 'completed');
|
|
241
241
|
}
|
|
242
242
|
else if (frame.method === 'thread/closed') {
|
|
243
243
|
this.withdrawOwner(threadId, null, events);
|
|
@@ -367,7 +367,7 @@ export class CodexProtocol {
|
|
|
367
367
|
this.pending.delete(requestId);
|
|
368
368
|
return { rpcId: pending.rpcId, result: { decision: codexDecision(decision, pending) } };
|
|
369
369
|
}
|
|
370
|
-
/*
|
|
370
|
+
/* An RPC reply, or plain text that continues the chat, for a question that still waits. */
|
|
371
371
|
questionAnswer(requestId, answers) {
|
|
372
372
|
const pending = this.pending.get(requestId);
|
|
373
373
|
if (pending?.type !== 'question') {
|
|
@@ -437,10 +437,10 @@ export class CodexProtocol {
|
|
|
437
437
|
this.toolInputs.delete(this.toolInputs.keys().next().value);
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
|
-
withdrawOwner(threadId, turnId, events) {
|
|
440
|
+
withdrawOwner(threadId, turnId, events, keepAsyncQuestions = false) {
|
|
441
441
|
const belongs = (owner) => owner.threadId === threadId && (turnId === null || owner.turnId === null || owner.turnId === turnId);
|
|
442
442
|
for (const [requestId, pending] of this.pending) {
|
|
443
|
-
if (belongs(pending)) {
|
|
443
|
+
if (belongs(pending) && !(keepAsyncQuestions && pending.type === 'question' && pending.async === true)) {
|
|
444
444
|
this.pending.delete(requestId);
|
|
445
445
|
events.push({ type: 'request.withdrawn', requestId });
|
|
446
446
|
}
|
|
@@ -598,7 +598,13 @@ export class CodexProtocol {
|
|
|
598
598
|
if (!completed || this.pending.has(ref)) {
|
|
599
599
|
return;
|
|
600
600
|
}
|
|
601
|
-
this.pending.set(ref, {
|
|
601
|
+
this.pending.set(ref, {
|
|
602
|
+
...this.owner(params),
|
|
603
|
+
type: 'question',
|
|
604
|
+
rpcId: null,
|
|
605
|
+
questionIds: questions.map((question) => question.id),
|
|
606
|
+
async: true
|
|
607
|
+
});
|
|
602
608
|
events.push({ type: 'question.requested', requestId: ref, questions, async: true });
|
|
603
609
|
return;
|
|
604
610
|
}
|
|
@@ -713,7 +719,7 @@ export class CodexProtocol {
|
|
|
713
719
|
const limit = status === 'failed' && isRecord(turn.error) ? this.limitOf(turn.error.codexErrorInfo) : null;
|
|
714
720
|
const turnId = str(turn.id) ?? this.codexTurnId;
|
|
715
721
|
this.codexTurnId = null;
|
|
716
|
-
this.withdrawOwner(str(params.threadId) ?? this.ownThreadId, turnId, events);
|
|
722
|
+
this.withdrawOwner(str(params.threadId) ?? this.ownThreadId, turnId, events, status === 'completed');
|
|
717
723
|
// A terminal session still open as the turn ends is one Codex left running; it only completes once its process does.
|
|
718
724
|
for (const [ref, processId] of this.openTerminals) {
|
|
719
725
|
this.backgroundTerminals.set(ref, processId);
|
|
@@ -790,7 +796,7 @@ export class CodexProtocol {
|
|
|
790
796
|
continue;
|
|
791
797
|
}
|
|
792
798
|
row.settled = true;
|
|
793
|
-
this.withdrawOwner(threadId, null, events);
|
|
799
|
+
this.withdrawOwner(threadId, null, events, status === 'completed');
|
|
794
800
|
settled = true;
|
|
795
801
|
events.push({ type: 'task.done', ref: row.ref, summary: collabAgentOutput({ [threadId]: state }), ok: status === 'completed' });
|
|
796
802
|
}
|
|
@@ -834,7 +840,7 @@ export class CodexProtocol {
|
|
|
834
840
|
}
|
|
835
841
|
if ((kind === 'completed' || kind === 'interrupted') && row !== undefined && !row.settled) {
|
|
836
842
|
row.settled = true;
|
|
837
|
-
this.withdrawOwner(threadId, null, events);
|
|
843
|
+
this.withdrawOwner(threadId, null, events, kind === 'completed');
|
|
838
844
|
events.push({ type: 'task.done', ref: row.ref, summary: null, ok: kind === 'completed' });
|
|
839
845
|
}
|
|
840
846
|
}
|
package/dist/chat/thread.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare class ChatThread {
|
|
|
5
5
|
private readonly order;
|
|
6
6
|
private historyGeneration;
|
|
7
7
|
private readonly indices;
|
|
8
|
+
private readonly requests;
|
|
8
9
|
private readonly holds;
|
|
9
10
|
constructor(info: ChatInfo, items?: ChatItem[]);
|
|
10
11
|
get(id: string): ChatItem | undefined;
|
|
@@ -25,6 +26,7 @@ export declare class ChatThread {
|
|
|
25
26
|
setStatus(status: AgentStatus): ChatEvent;
|
|
26
27
|
statusFor(activeTurnId: string | null): AgentStatus;
|
|
27
28
|
pending(): Array<ChatApprovalItem | ChatQuestionItem>;
|
|
29
|
+
private indexRequest;
|
|
28
30
|
private syncRequests;
|
|
29
31
|
history(limit?: number, cursor?: string): ChatHistoryResult;
|
|
30
32
|
subagentsBefore(start: number): ChatSubagentItem[];
|
package/dist/chat/thread.js
CHANGED
|
@@ -13,6 +13,7 @@ export class ChatThread {
|
|
|
13
13
|
order = [];
|
|
14
14
|
historyGeneration = randomUUID();
|
|
15
15
|
indices = new Map();
|
|
16
|
+
requests = new Map();
|
|
16
17
|
// What the host asks a person beside the CLI's own requests, such as a card a verb waits on.
|
|
17
18
|
holds = new Set();
|
|
18
19
|
constructor(info, items = []) {
|
|
@@ -21,6 +22,7 @@ export class ChatThread {
|
|
|
21
22
|
this.items.set(item.id, item);
|
|
22
23
|
this.indices.set(item.id, this.order.length);
|
|
23
24
|
this.order.push(item.id);
|
|
25
|
+
this.indexRequest(item);
|
|
24
26
|
}
|
|
25
27
|
this.syncRequests();
|
|
26
28
|
}
|
|
@@ -45,7 +47,9 @@ export class ChatThread {
|
|
|
45
47
|
this.order.push(item.id);
|
|
46
48
|
}
|
|
47
49
|
this.items.set(item.id, item);
|
|
48
|
-
|
|
50
|
+
const wasPending = this.requests.has(item.id);
|
|
51
|
+
this.indexRequest(item);
|
|
52
|
+
if (wasPending || item.kind === 'approval' || item.kind === 'question') {
|
|
49
53
|
this.syncRequests();
|
|
50
54
|
}
|
|
51
55
|
return { type: 'item', item, historyIndex: this.indices.get(item.id) };
|
|
@@ -89,6 +93,7 @@ export class ChatThread {
|
|
|
89
93
|
reset(patch) {
|
|
90
94
|
this.items.clear();
|
|
91
95
|
this.indices.clear();
|
|
96
|
+
this.requests.clear();
|
|
92
97
|
this.historyGeneration = randomUUID();
|
|
93
98
|
this.order.length = 0;
|
|
94
99
|
this.info = { ...this.info, ...patch };
|
|
@@ -126,13 +131,21 @@ export class ChatThread {
|
|
|
126
131
|
}
|
|
127
132
|
/* A request can outlive the turn it came in, when work the CLI runs beside its turns asked it. */
|
|
128
133
|
statusFor(activeTurnId) {
|
|
129
|
-
if (this.
|
|
134
|
+
if (this.requests.size > 0 || this.holds.size > 0) {
|
|
130
135
|
return 'needs-you';
|
|
131
136
|
}
|
|
132
137
|
return activeTurnId === null ? 'idle' : 'running';
|
|
133
138
|
}
|
|
134
139
|
pending() {
|
|
135
|
-
return this.
|
|
140
|
+
return [...this.requests.values()].sort((left, right) => this.indices.get(left.id) - this.indices.get(right.id));
|
|
141
|
+
}
|
|
142
|
+
indexRequest(item) {
|
|
143
|
+
if ((item.kind === 'approval' && item.decision === 'pending') || (item.kind === 'question' && item.state === 'pending')) {
|
|
144
|
+
this.requests.set(item.id, item);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
this.requests.delete(item.id);
|
|
148
|
+
}
|
|
136
149
|
}
|
|
137
150
|
/*
|
|
138
151
|
* `info.requests` follows the request items, so whatever event comes next carries what the chat waits on,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adecore/agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.37.0",
|
|
4
4
|
"description": "Node-compatible agent chat hosts, providers, accounts, usage and durable task coordination.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"test": "bun --conditions=source test"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@adecore/agent-contracts": "0.
|
|
85
|
-
"@adecore/intelligent-ui": "0.
|
|
84
|
+
"@adecore/agent-contracts": "0.37.0",
|
|
85
|
+
"@adecore/intelligent-ui": "0.37.0",
|
|
86
86
|
"zod": "^4.6.5"
|
|
87
87
|
},
|
|
88
88
|
"devDependencies": {
|
|
@@ -503,8 +503,11 @@ export class CodexBackend implements ChatBackend {
|
|
|
503
503
|
}
|
|
504
504
|
const turnId = this.protocol.turnId;
|
|
505
505
|
if (!turnId) {
|
|
506
|
-
//
|
|
507
|
-
|
|
506
|
+
// An optional question can outlive its turn; its answer resumes the same chat as plain text.
|
|
507
|
+
this.turnUnnamed = true;
|
|
508
|
+
this.promptId = null;
|
|
509
|
+
this.request('turn/start', this.turnStartParams(textInput(answer.text)));
|
|
510
|
+
return true;
|
|
508
511
|
}
|
|
509
512
|
this.request('turn/steer', { threadId: this.threadId, expectedTurnId: turnId, input: textInput(answer.text) });
|
|
510
513
|
return true;
|
|
@@ -516,6 +519,9 @@ export class CodexBackend implements ChatBackend {
|
|
|
516
519
|
}
|
|
517
520
|
|
|
518
521
|
declineRequest(requestId: string, _message: string): boolean {
|
|
522
|
+
if (this.protocol.dismissQuestion(requestId)) {
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
519
525
|
const answer = this.protocol.declineRequest(requestId);
|
|
520
526
|
if (!this.transport || !answer) {
|
|
521
527
|
return false;
|
|
@@ -285,7 +285,12 @@ export class CodexProtocol {
|
|
|
285
285
|
const threadId = frame.method === 'thread/started' ? (isRecord(params.thread) ? str(params.thread.id) : null) : str(params.threadId);
|
|
286
286
|
if (threadId !== null && this.ownThreadId !== null && threadId !== this.ownThreadId) {
|
|
287
287
|
if (frame.method === 'turn/completed') {
|
|
288
|
-
this.withdrawOwner(
|
|
288
|
+
this.withdrawOwner(
|
|
289
|
+
threadId,
|
|
290
|
+
isRecord(params.turn) ? str(params.turn.id) : null,
|
|
291
|
+
events,
|
|
292
|
+
isRecord(params.turn) && params.turn.status === 'completed'
|
|
293
|
+
);
|
|
289
294
|
} else if (frame.method === 'thread/closed') {
|
|
290
295
|
this.withdrawOwner(threadId, null, events);
|
|
291
296
|
}
|
|
@@ -416,7 +421,7 @@ export class CodexProtocol {
|
|
|
416
421
|
return { rpcId: pending.rpcId, result: { decision: codexDecision(decision, pending) } };
|
|
417
422
|
}
|
|
418
423
|
|
|
419
|
-
/*
|
|
424
|
+
/* An RPC reply, or plain text that continues the chat, for a question that still waits. */
|
|
420
425
|
questionAnswer(requestId: string, answers: Record<string, string>): CodexAnswer | null {
|
|
421
426
|
const pending = this.pending.get(requestId);
|
|
422
427
|
if (pending?.type !== 'question') {
|
|
@@ -494,10 +499,10 @@ export class CodexProtocol {
|
|
|
494
499
|
}
|
|
495
500
|
}
|
|
496
501
|
|
|
497
|
-
private withdrawOwner(threadId: string | null, turnId: string | null, events: BackendEvent[]): void {
|
|
502
|
+
private withdrawOwner(threadId: string | null, turnId: string | null, events: BackendEvent[], keepAsyncQuestions = false): void {
|
|
498
503
|
const belongs = (owner: RequestOwner): boolean => owner.threadId === threadId && (turnId === null || owner.turnId === null || owner.turnId === turnId);
|
|
499
504
|
for (const [requestId, pending] of this.pending) {
|
|
500
|
-
if (belongs(pending)) {
|
|
505
|
+
if (belongs(pending) && !(keepAsyncQuestions && pending.type === 'question' && pending.async === true)) {
|
|
501
506
|
this.pending.delete(requestId);
|
|
502
507
|
events.push({ type: 'request.withdrawn', requestId });
|
|
503
508
|
}
|
|
@@ -664,7 +669,13 @@ export class CodexProtocol {
|
|
|
664
669
|
if (!completed || this.pending.has(ref)) {
|
|
665
670
|
return;
|
|
666
671
|
}
|
|
667
|
-
this.pending.set(ref, {
|
|
672
|
+
this.pending.set(ref, {
|
|
673
|
+
...this.owner(params),
|
|
674
|
+
type: 'question',
|
|
675
|
+
rpcId: null,
|
|
676
|
+
questionIds: questions.map((question) => question.id),
|
|
677
|
+
async: true
|
|
678
|
+
});
|
|
668
679
|
events.push({ type: 'question.requested', requestId: ref, questions, async: true });
|
|
669
680
|
return;
|
|
670
681
|
}
|
|
@@ -805,7 +816,7 @@ export class CodexProtocol {
|
|
|
805
816
|
const limit = status === 'failed' && isRecord(turn.error) ? this.limitOf(turn.error.codexErrorInfo) : null;
|
|
806
817
|
const turnId = str(turn.id) ?? this.codexTurnId;
|
|
807
818
|
this.codexTurnId = null;
|
|
808
|
-
this.withdrawOwner(str(params.threadId) ?? this.ownThreadId, turnId, events);
|
|
819
|
+
this.withdrawOwner(str(params.threadId) ?? this.ownThreadId, turnId, events, status === 'completed');
|
|
809
820
|
// A terminal session still open as the turn ends is one Codex left running; it only completes once its process does.
|
|
810
821
|
for (const [ref, processId] of this.openTerminals) {
|
|
811
822
|
this.backgroundTerminals.set(ref, processId);
|
|
@@ -886,7 +897,7 @@ export class CodexProtocol {
|
|
|
886
897
|
continue;
|
|
887
898
|
}
|
|
888
899
|
row.settled = true;
|
|
889
|
-
this.withdrawOwner(threadId, null, events);
|
|
900
|
+
this.withdrawOwner(threadId, null, events, status === 'completed');
|
|
890
901
|
settled = true;
|
|
891
902
|
events.push({ type: 'task.done', ref: row.ref, summary: collabAgentOutput({ [threadId]: state }), ok: status === 'completed' });
|
|
892
903
|
}
|
|
@@ -931,7 +942,7 @@ export class CodexProtocol {
|
|
|
931
942
|
}
|
|
932
943
|
if ((kind === 'completed' || kind === 'interrupted') && row !== undefined && !row.settled) {
|
|
933
944
|
row.settled = true;
|
|
934
|
-
this.withdrawOwner(threadId, null, events);
|
|
945
|
+
this.withdrawOwner(threadId, null, events, kind === 'completed');
|
|
935
946
|
events.push({ type: 'task.done', ref: row.ref, summary: null, ok: kind === 'completed' });
|
|
936
947
|
}
|
|
937
948
|
}
|
package/src/chat/thread.ts
CHANGED
|
@@ -24,6 +24,7 @@ export class ChatThread {
|
|
|
24
24
|
private readonly order: string[] = [];
|
|
25
25
|
private historyGeneration = randomUUID();
|
|
26
26
|
private readonly indices = new Map<string, number>();
|
|
27
|
+
private readonly requests = new Map<string, ChatApprovalItem | ChatQuestionItem>();
|
|
27
28
|
// What the host asks a person beside the CLI's own requests, such as a card a verb waits on.
|
|
28
29
|
private readonly holds = new Set<string>();
|
|
29
30
|
|
|
@@ -33,6 +34,7 @@ export class ChatThread {
|
|
|
33
34
|
this.items.set(item.id, item);
|
|
34
35
|
this.indices.set(item.id, this.order.length);
|
|
35
36
|
this.order.push(item.id);
|
|
37
|
+
this.indexRequest(item);
|
|
36
38
|
}
|
|
37
39
|
this.syncRequests();
|
|
38
40
|
}
|
|
@@ -61,7 +63,9 @@ export class ChatThread {
|
|
|
61
63
|
this.order.push(item.id);
|
|
62
64
|
}
|
|
63
65
|
this.items.set(item.id, item);
|
|
64
|
-
|
|
66
|
+
const wasPending = this.requests.has(item.id);
|
|
67
|
+
this.indexRequest(item);
|
|
68
|
+
if (wasPending || item.kind === 'approval' || item.kind === 'question') {
|
|
65
69
|
this.syncRequests();
|
|
66
70
|
}
|
|
67
71
|
return { type: 'item', item, historyIndex: this.indices.get(item.id)! };
|
|
@@ -111,6 +115,7 @@ export class ChatThread {
|
|
|
111
115
|
reset(patch: Partial<ChatInfo>): ChatEvent {
|
|
112
116
|
this.items.clear();
|
|
113
117
|
this.indices.clear();
|
|
118
|
+
this.requests.clear();
|
|
114
119
|
this.historyGeneration = randomUUID();
|
|
115
120
|
this.order.length = 0;
|
|
116
121
|
this.info = { ...this.info, ...patch };
|
|
@@ -151,17 +156,22 @@ export class ChatThread {
|
|
|
151
156
|
|
|
152
157
|
/* A request can outlive the turn it came in, when work the CLI runs beside its turns asked it. */
|
|
153
158
|
statusFor(activeTurnId: string | null): AgentStatus {
|
|
154
|
-
if (this.
|
|
159
|
+
if (this.requests.size > 0 || this.holds.size > 0) {
|
|
155
160
|
return 'needs-you';
|
|
156
161
|
}
|
|
157
162
|
return activeTurnId === null ? 'idle' : 'running';
|
|
158
163
|
}
|
|
159
164
|
|
|
160
165
|
pending(): Array<ChatApprovalItem | ChatQuestionItem> {
|
|
161
|
-
return this.
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
166
|
+
return [...this.requests.values()].sort((left, right) => this.indices.get(left.id)! - this.indices.get(right.id)!);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private indexRequest(item: ChatItem): void {
|
|
170
|
+
if ((item.kind === 'approval' && item.decision === 'pending') || (item.kind === 'question' && item.state === 'pending')) {
|
|
171
|
+
this.requests.set(item.id, item);
|
|
172
|
+
} else {
|
|
173
|
+
this.requests.delete(item.id);
|
|
174
|
+
}
|
|
165
175
|
}
|
|
166
176
|
|
|
167
177
|
/*
|