@canonmsg/codex-plugin 0.19.0 → 0.19.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/dist/app-server-adapter.d.ts +7 -0
- package/dist/app-server-adapter.js +15 -2
- package/dist/host.js +28 -6
- package/dist/turn-activity.d.ts +9 -0
- package/dist/turn-activity.js +10 -0
- package/package.json +5 -5
|
@@ -84,6 +84,13 @@ export declare class CodexAppServerAdapter {
|
|
|
84
84
|
private handleLine;
|
|
85
85
|
private handleServerRequest;
|
|
86
86
|
private handleNotification;
|
|
87
|
+
/**
|
|
88
|
+
* The turn's final message is the JOIN of every agentMessage item, in
|
|
89
|
+
* insertion order — not just the last item. The streamed turn trail folds
|
|
90
|
+
* all items (one text segment per item), so the final text must match the
|
|
91
|
+
* streamed/folded text or the app renders the turn twice.
|
|
92
|
+
*/
|
|
93
|
+
private joinedAgentMessageText;
|
|
87
94
|
private isCurrentThreadNotification;
|
|
88
95
|
private resolveCurrentTurn;
|
|
89
96
|
private clearActiveTurn;
|
|
@@ -393,7 +393,7 @@ export class CodexAppServerAdapter {
|
|
|
393
393
|
const delta = readRawString(params, 'delta') ?? '';
|
|
394
394
|
const next = `${this.messageTextByItem.get(itemId) ?? ''}${delta}`;
|
|
395
395
|
this.messageTextByItem.set(itemId, next);
|
|
396
|
-
this.currentFinalMessage =
|
|
396
|
+
this.currentFinalMessage = this.joinedAgentMessageText() ?? this.currentFinalMessage;
|
|
397
397
|
if (next.trim())
|
|
398
398
|
this.currentOnEvent?.({ type: 'message', text: next, itemId });
|
|
399
399
|
return;
|
|
@@ -432,7 +432,8 @@ export class CodexAppServerAdapter {
|
|
|
432
432
|
const itemId = readString(item, 'id');
|
|
433
433
|
const text = readString(item, 'text');
|
|
434
434
|
if (text) {
|
|
435
|
-
this.
|
|
435
|
+
this.messageTextByItem.set(itemId ?? 'agent-message', text);
|
|
436
|
+
this.currentFinalMessage = this.joinedAgentMessageText() ?? text;
|
|
436
437
|
this.currentOnEvent?.({
|
|
437
438
|
type: 'message',
|
|
438
439
|
text,
|
|
@@ -486,6 +487,18 @@ export class CodexAppServerAdapter {
|
|
|
486
487
|
this.currentErrorText = stringifyPreview(params);
|
|
487
488
|
}
|
|
488
489
|
}
|
|
490
|
+
/**
|
|
491
|
+
* The turn's final message is the JOIN of every agentMessage item, in
|
|
492
|
+
* insertion order — not just the last item. The streamed turn trail folds
|
|
493
|
+
* all items (one text segment per item), so the final text must match the
|
|
494
|
+
* streamed/folded text or the app renders the turn twice.
|
|
495
|
+
*/
|
|
496
|
+
joinedAgentMessageText() {
|
|
497
|
+
const joined = [...this.messageTextByItem.values()]
|
|
498
|
+
.filter((text) => text.trim())
|
|
499
|
+
.join('\n\n');
|
|
500
|
+
return joined.trim() ? joined : null;
|
|
501
|
+
}
|
|
489
502
|
isCurrentThreadNotification(params) {
|
|
490
503
|
const threadId = readNotificationThreadId(params);
|
|
491
504
|
if (threadId && this.threadId && threadId !== this.threadId)
|
package/dist/host.js
CHANGED
|
@@ -26,7 +26,7 @@ import { detectCodexCliVersion } from './codex-cli-version.js';
|
|
|
26
26
|
import { buildCodexModelGuardMessage, formatCodexTurnFailure, isRecoverableCodexThreadError, } from './error-format.js';
|
|
27
27
|
import { attachCodexControlNotifications } from './control-channel.js';
|
|
28
28
|
import { runCli } from './cli-entry.js';
|
|
29
|
-
import { beginCommandBlock, claimCommandBlock, createCommandBlockTracker, textSegmentBlockId, } from './turn-activity.js';
|
|
29
|
+
import { beginCommandBlock, claimCommandBlock, createCommandBlockTracker, hasSpeechSegmentText, textSegmentBlockId, } from './turn-activity.js';
|
|
30
30
|
const HELP = `canon-codex — run a local Codex agent host for Canon
|
|
31
31
|
|
|
32
32
|
USAGE
|
|
@@ -990,7 +990,16 @@ export async function main() {
|
|
|
990
990
|
title: 'Plan',
|
|
991
991
|
text: event.text,
|
|
992
992
|
});
|
|
993
|
-
writers.streamingOutput.
|
|
993
|
+
if (hasSpeechSegmentText(writers.streamingOutput.getBlocks())) {
|
|
994
|
+
// Speech is already flowing: publish the staged plan block
|
|
995
|
+
// only. Replacing the whole snapshot with plan text would be
|
|
996
|
+
// undone by the next agentMessage snapshot and make the
|
|
997
|
+
// smoothed bubble snap back and forth.
|
|
998
|
+
writers.streamingOutput.flush().catch(() => { });
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
writers.streamingOutput.replaceSnapshot(event.text, 'streaming').catch(() => { });
|
|
1002
|
+
}
|
|
994
1003
|
return;
|
|
995
1004
|
}
|
|
996
1005
|
if (event.type === 'waiting') {
|
|
@@ -1001,9 +1010,6 @@ export async function main() {
|
|
|
1001
1010
|
return;
|
|
1002
1011
|
}
|
|
1003
1012
|
if (event.type === 'command.started') {
|
|
1004
|
-
session.turnState = 'tool';
|
|
1005
|
-
writers.writeTurn();
|
|
1006
|
-
writers.startVisibleWorkSignal();
|
|
1007
1013
|
const blockId = beginCommandBlock(session.turnCommandBlocks, {
|
|
1008
1014
|
turnId: session.currentTurnId,
|
|
1009
1015
|
command: event.command,
|
|
@@ -1016,6 +1022,16 @@ export async function main() {
|
|
|
1016
1022
|
title: summarizeCommand(event.command),
|
|
1017
1023
|
summary: 'Command running',
|
|
1018
1024
|
});
|
|
1025
|
+
if (hasSpeechSegmentText(writers.streamingOutput.getBlocks())) {
|
|
1026
|
+
// Speech is already flowing: the staged tool block narrates
|
|
1027
|
+
// the command in the margin. Publish it without flapping the
|
|
1028
|
+
// bubble status away from 'streaming'.
|
|
1029
|
+
writers.streamingOutput.flush().catch(() => { });
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
session.turnState = 'tool';
|
|
1033
|
+
writers.writeTurn();
|
|
1034
|
+
writers.startVisibleWorkSignal();
|
|
1019
1035
|
writers.streamingOutput.setStatus('tool').catch(() => { });
|
|
1020
1036
|
return;
|
|
1021
1037
|
}
|
|
@@ -1034,12 +1050,18 @@ export async function main() {
|
|
|
1034
1050
|
summary: 'Command completed',
|
|
1035
1051
|
});
|
|
1036
1052
|
}
|
|
1037
|
-
if (session.turnState === 'tool'
|
|
1053
|
+
if (session.turnState === 'tool'
|
|
1054
|
+
&& !hasSpeechSegmentText(writers.streamingOutput.getBlocks())) {
|
|
1038
1055
|
session.turnState = 'thinking';
|
|
1039
1056
|
writers.writeTurn();
|
|
1040
1057
|
writers.startVisibleWorkSignal();
|
|
1041
1058
|
writers.streamingOutput.setStatus('thinking').catch(() => { });
|
|
1042
1059
|
}
|
|
1060
|
+
else {
|
|
1061
|
+
// Speech is flowing (or the turn never left streaming):
|
|
1062
|
+
// publish the staged completion without a status flap.
|
|
1063
|
+
writers.streamingOutput.flush().catch(() => { });
|
|
1064
|
+
}
|
|
1043
1065
|
return;
|
|
1044
1066
|
}
|
|
1045
1067
|
if (event.type === 'turn.completed') {
|
package/dist/turn-activity.d.ts
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
|
+
import type { TurnOutputBlock } from '@canonmsg/core/contract';
|
|
1
2
|
interface RunningCommandBlock {
|
|
2
3
|
command: string;
|
|
3
4
|
blockId: string;
|
|
4
5
|
itemId?: string;
|
|
5
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* True once the streaming output already carries assistant speech (folded
|
|
9
|
+
* text segments). While speech is flowing the host must not flap the bubble
|
|
10
|
+
* status to tool/thinking or clobber the live text with plan snapshots — the
|
|
11
|
+
* staged margin blocks already narrate that activity, and every status/text
|
|
12
|
+
* swap makes the smoothed bubble snap.
|
|
13
|
+
*/
|
|
14
|
+
export declare function hasSpeechSegmentText(blocks: ReadonlyArray<Pick<TurnOutputBlock, 'kind' | 'text'>>): boolean;
|
|
6
15
|
export interface CommandBlockTracker {
|
|
7
16
|
sequence: number;
|
|
8
17
|
running: RunningCommandBlock[];
|
package/dist/turn-activity.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True once the streaming output already carries assistant speech (folded
|
|
3
|
+
* text segments). While speech is flowing the host must not flap the bubble
|
|
4
|
+
* status to tool/thinking or clobber the live text with plan snapshots — the
|
|
5
|
+
* staged margin blocks already narrate that activity, and every status/text
|
|
6
|
+
* swap makes the smoothed bubble snap.
|
|
7
|
+
*/
|
|
8
|
+
export function hasSpeechSegmentText(blocks) {
|
|
9
|
+
return blocks.some((block) => block.kind === 'text' && Boolean(block.text?.trim()));
|
|
10
|
+
}
|
|
1
11
|
export function createCommandBlockTracker() {
|
|
2
12
|
return {
|
|
3
13
|
sequence: 0,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@canonmsg/agent-host": "^0.3.0",
|
|
33
33
|
"@canonmsg/agent-sdk": "^3.4.3",
|
|
34
|
-
"@canonmsg/backend-contracts": "^1.8.
|
|
35
|
-
"@canonmsg/bridge": "^0.2.
|
|
36
|
-
"@canonmsg/core": "^3.1.
|
|
37
|
-
"@canonmsg/framework": "^0.2.
|
|
34
|
+
"@canonmsg/backend-contracts": "^1.8.1",
|
|
35
|
+
"@canonmsg/bridge": "^0.2.1",
|
|
36
|
+
"@canonmsg/core": "^3.1.1",
|
|
37
|
+
"@canonmsg/framework": "^0.2.1"
|
|
38
38
|
},
|
|
39
39
|
"engines": {
|
|
40
40
|
"node": ">=18.0.0"
|