@canonmsg/codex-plugin 0.18.4 → 0.18.6
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/adapter.d.ts +1 -0
- package/dist/adapter.js +7 -1
- package/dist/app-server-adapter.js +7 -2
- package/dist/host.d.ts +17 -0
- package/dist/host.js +89 -41
- package/dist/turn-activity.d.ts +11 -0
- package/dist/turn-activity.js +43 -0
- package/package.json +4 -4
package/dist/adapter.d.ts
CHANGED
package/dist/adapter.js
CHANGED
|
@@ -99,7 +99,13 @@ export class CodexConversationAdapter {
|
|
|
99
99
|
if (event.item?.type === 'agent_message') {
|
|
100
100
|
latestMessage = normalizeMessageText(event.item.text);
|
|
101
101
|
if (latestMessage) {
|
|
102
|
-
onEvent({
|
|
102
|
+
onEvent({
|
|
103
|
+
type: 'message',
|
|
104
|
+
text: latestMessage,
|
|
105
|
+
...(typeof event.item.id === 'string' && event.item.id.trim()
|
|
106
|
+
? { itemId: event.item.id.trim() }
|
|
107
|
+
: {}),
|
|
108
|
+
});
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
else if (event.item?.type === 'command_execution') {
|
|
@@ -338,7 +338,7 @@ export class CodexAppServerAdapter {
|
|
|
338
338
|
this.messageTextByItem.set(itemId, next);
|
|
339
339
|
this.currentFinalMessage = next.trim() ? next : this.currentFinalMessage;
|
|
340
340
|
if (next.trim())
|
|
341
|
-
this.currentOnEvent?.({ type: 'message', text: next });
|
|
341
|
+
this.currentOnEvent?.({ type: 'message', text: next, itemId });
|
|
342
342
|
return;
|
|
343
343
|
}
|
|
344
344
|
if (method === 'turn/plan/updated') {
|
|
@@ -372,10 +372,15 @@ export class CodexAppServerAdapter {
|
|
|
372
372
|
if (method === 'item/completed') {
|
|
373
373
|
const item = params.item;
|
|
374
374
|
if (item?.type === 'agentMessage') {
|
|
375
|
+
const itemId = readString(item, 'id');
|
|
375
376
|
const text = readString(item, 'text');
|
|
376
377
|
if (text) {
|
|
377
378
|
this.currentFinalMessage = text;
|
|
378
|
-
this.currentOnEvent?.({
|
|
379
|
+
this.currentOnEvent?.({
|
|
380
|
+
type: 'message',
|
|
381
|
+
text,
|
|
382
|
+
...(itemId ? { itemId } : {}),
|
|
383
|
+
});
|
|
379
384
|
}
|
|
380
385
|
}
|
|
381
386
|
else if (item?.type === 'commandExecution') {
|
package/dist/host.d.ts
CHANGED
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
interface HostSessionState {
|
|
3
|
+
lastError?: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
permissionMode?: string;
|
|
6
|
+
effort?: string;
|
|
7
|
+
/** Tokens consumed by the latest completed turn; Codex reports no context window. */
|
|
8
|
+
contextUsage?: {
|
|
9
|
+
totalTokens: number;
|
|
10
|
+
};
|
|
11
|
+
state: 'idle' | 'running';
|
|
12
|
+
}
|
|
13
|
+
export declare function buildCodexInitialSessionState(input: {
|
|
14
|
+
model?: string;
|
|
15
|
+
permissionMode?: string;
|
|
16
|
+
effort?: string | null;
|
|
17
|
+
}): HostSessionState;
|
|
2
18
|
/** GPT reasoning-effort levels, applied next turn via model_reasoning_effort. */
|
|
3
19
|
export declare const CODEX_EFFORT_OPTIONS: readonly [{
|
|
4
20
|
readonly value: "minimal";
|
|
@@ -17,3 +33,4 @@ export declare const CODEX_EFFORT_OPTIONS: readonly [{
|
|
|
17
33
|
readonly label: "Extra high";
|
|
18
34
|
}];
|
|
19
35
|
export declare function main(): Promise<void>;
|
|
36
|
+
export {};
|
package/dist/host.js
CHANGED
|
@@ -5,8 +5,8 @@ import { spawnSync } from 'node:child_process';
|
|
|
5
5
|
import { dirname } from 'node:path';
|
|
6
6
|
import { parseArgs } from 'node:util';
|
|
7
7
|
import { getCodexImagePath, materializeMessageMedia, materializeReplyContextMedia, sendMediaFileMessage, } from '@canonmsg/agent-sdk';
|
|
8
|
-
import {
|
|
9
|
-
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, resolveQuestionAllowOther, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent,
|
|
8
|
+
import { captureTurnArtifactSnapshot, collectTurnArtifacts, } from '@canonmsg/coding-agent-host';
|
|
9
|
+
import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_ACTION, buildRuntimeCardOutcome, buildPlanApprovalRequest, resolveQuestionAllowOther, buildCanonTurnContextV2, buildConfiguredWorkspaceOptionsWithRoots, buildFirstPartyCodingRuntimeDescriptor, buildHydratedInboundContext, diffCanonMemberIds, buildPublicWorkspaceRoots, buildPublicWorkspaceOptions, buildRuntimePresentationPolicy, buildCanonInboundFrameV1, DEFAULT_FIRST_PARTY_RUNTIME_PRESENTATION, createConversationMetadataLoader, createRuntimeStatePublisher, createTypingStatusPublisher, EXECUTION_ENVIRONMENT_MODES, ExecutionEnvironmentError, CanonClient, CanonStream, DEFAULT_PARTICIPATION_HISTORY_FETCH_LIMIT, DEFAULT_RUNTIME_CAPABILITIES, FINAL_MESSAGE_HANDOFF_MS, getActiveProfileLock, initRTDBAuth, buildLocalRuntimeId, heartbeatLocalRuntimeEntry, loadRuntimeSessionState, markLocalRuntimeStopped, normalizeTurnMetadata, parseRuntimeCardV1, prepareConversationEnvironment, loadHostSessionConfig, releaseConversationEnvironment, resolveCanonAgent, CanonApiError, sendMessageWithRetry, sendMessageWithRetryChunked, shouldTriggerAgentTurn, saveRuntimeSessionState, buildBoundedTurnTrail, publishHostAgentRuntime, publishHostSessionSnapshots, renderCanonHostInboundContent, renderCodingHostInboundPrompt, resolveHostWorkspaceCwd, upsertLocalRuntimeEntry, } from '@canonmsg/core';
|
|
10
10
|
import { decideAutoReply, } from './inbound-policy.js';
|
|
11
11
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
12
12
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
@@ -19,7 +19,7 @@ import { startCodexStreamInBackground } from './host-lifecycle.js';
|
|
|
19
19
|
import { createCodexControlPoller } from './control-channel.js';
|
|
20
20
|
import { runCli } from './cli-entry.js';
|
|
21
21
|
import { collectMissedInboundMessages, STARTUP_RECOVERY_MAX_MESSAGES, STARTUP_RECOVERY_PAGE_SIZE, } from './startup-recovery.js';
|
|
22
|
-
import { beginCommandBlock, claimCommandBlock, createCommandBlockTracker, } from './turn-activity.js';
|
|
22
|
+
import { applyTextSegmentBlock, beginCommandBlock, claimCommandBlock, createCommandBlockTracker, } from './turn-activity.js';
|
|
23
23
|
const HELP = `canon-codex — run a local Codex agent host for Canon
|
|
24
24
|
|
|
25
25
|
USAGE
|
|
@@ -53,6 +53,14 @@ Keep this terminal open while you want Canon to reach the agent. Closing it,
|
|
|
53
53
|
logging out, rebooting, or sleeping long enough to stop the process takes the
|
|
54
54
|
local agent offline until you revive it. Docs:
|
|
55
55
|
https://canonmail.com/agents/integrations`;
|
|
56
|
+
export function buildCodexInitialSessionState(input) {
|
|
57
|
+
return {
|
|
58
|
+
model: input.model,
|
|
59
|
+
permissionMode: input.permissionMode,
|
|
60
|
+
...(input.effort ? { effort: input.effort } : {}),
|
|
61
|
+
state: 'idle',
|
|
62
|
+
};
|
|
63
|
+
}
|
|
56
64
|
const MAX_SESSIONS = 12;
|
|
57
65
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
58
66
|
const HEARTBEAT_MS = 30_000;
|
|
@@ -248,7 +256,7 @@ function resolveCodexEffectiveRuntimePolicy(input) {
|
|
|
248
256
|
};
|
|
249
257
|
}
|
|
250
258
|
function buildCanonPrompt(input) {
|
|
251
|
-
return
|
|
259
|
+
return renderCodingHostInboundPrompt(buildCanonInboundFrameV1(buildCanonTurnContextV2({
|
|
252
260
|
content: input.content,
|
|
253
261
|
conversationId: input.conversationId,
|
|
254
262
|
participantContext: input.participantContext,
|
|
@@ -257,9 +265,8 @@ function buildCanonPrompt(input) {
|
|
|
257
265
|
activeSelfContextId: input.activeSelfContextId,
|
|
258
266
|
provenance: input.provenance,
|
|
259
267
|
replyContext: input.replyContext,
|
|
260
|
-
sessionContextLines: input.sessionContextLines,
|
|
261
268
|
message: input.message,
|
|
262
|
-
}));
|
|
269
|
+
})));
|
|
263
270
|
}
|
|
264
271
|
function renderInboundContent(message, materialized) {
|
|
265
272
|
return renderCanonHostInboundContent(message, materialized);
|
|
@@ -668,6 +675,18 @@ export async function main() {
|
|
|
668
675
|
blocks: session.turnBlocks,
|
|
669
676
|
}).catch(() => { });
|
|
670
677
|
}
|
|
678
|
+
function upsertCodexTextSegment(session, event) {
|
|
679
|
+
const next = applyTextSegmentBlock({
|
|
680
|
+
turnLiveText: session.turnLiveText,
|
|
681
|
+
turnBlocks: session.turnBlocks,
|
|
682
|
+
}, {
|
|
683
|
+
turnId: session.currentTurnId,
|
|
684
|
+
itemId: event.itemId,
|
|
685
|
+
text: event.text,
|
|
686
|
+
});
|
|
687
|
+
session.turnLiveText = next.turnLiveText;
|
|
688
|
+
session.turnBlocks = next.turnBlocks;
|
|
689
|
+
}
|
|
671
690
|
function upsertTurnBlock(session, block) {
|
|
672
691
|
const now = Date.now();
|
|
673
692
|
const index = session.turnBlocks.findIndex((existing) => existing.id === block.id);
|
|
@@ -867,11 +886,11 @@ export async function main() {
|
|
|
867
886
|
adapter,
|
|
868
887
|
queue: [],
|
|
869
888
|
running: false,
|
|
870
|
-
state: {
|
|
889
|
+
state: buildCodexInitialSessionState({
|
|
871
890
|
model: policy.model,
|
|
872
891
|
permissionMode: policy.permissionMode,
|
|
873
|
-
|
|
874
|
-
},
|
|
892
|
+
effort: initialEffort,
|
|
893
|
+
}),
|
|
875
894
|
policyFingerprint: policy.fingerprint,
|
|
876
895
|
turnState: 'idle',
|
|
877
896
|
currentTurnId: null,
|
|
@@ -907,8 +926,17 @@ export async function main() {
|
|
|
907
926
|
pendingSessionCreations.delete(conversationId);
|
|
908
927
|
}
|
|
909
928
|
}
|
|
910
|
-
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false) {
|
|
911
|
-
const nextPrompt = {
|
|
929
|
+
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false, artifactRoutingMode = 'disabled') {
|
|
930
|
+
const nextPrompt = {
|
|
931
|
+
prompt,
|
|
932
|
+
intent,
|
|
933
|
+
sourceMessageId,
|
|
934
|
+
markAccepted,
|
|
935
|
+
imagePaths,
|
|
936
|
+
mediaAddDirs,
|
|
937
|
+
planMode,
|
|
938
|
+
artifactRoutingMode,
|
|
939
|
+
};
|
|
912
940
|
if (toFront) {
|
|
913
941
|
session.queue.unshift(nextPrompt);
|
|
914
942
|
}
|
|
@@ -919,6 +947,11 @@ export async function main() {
|
|
|
919
947
|
writeTurn(session);
|
|
920
948
|
void runNextTurn(session);
|
|
921
949
|
}
|
|
950
|
+
function resolveArtifactRoutingMode(participantContext) {
|
|
951
|
+
return participantContext.conversationType === 'direct' && participantContext.isOwner
|
|
952
|
+
? 'workspace-generated'
|
|
953
|
+
: 'disabled';
|
|
954
|
+
}
|
|
922
955
|
async function waitForRuntimeInputResponse(input) {
|
|
923
956
|
while (Date.now() < input.expiresAt) {
|
|
924
957
|
const response = await client.consumeRuntimeInputResponse({
|
|
@@ -1291,43 +1324,50 @@ export async function main() {
|
|
|
1291
1324
|
message: input.message,
|
|
1292
1325
|
});
|
|
1293
1326
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1294
|
-
|
|
1327
|
+
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1328
|
+
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1295
1329
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1296
1330
|
await session.adapter.interrupt().catch(() => { });
|
|
1297
1331
|
clearStreaming(input.conversationId);
|
|
1298
1332
|
typingSignals.clear(input.conversationId).catch(() => { });
|
|
1299
1333
|
return;
|
|
1300
1334
|
}
|
|
1301
|
-
|
|
1335
|
+
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1336
|
+
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1337
|
+
}
|
|
1338
|
+
function sendTurnArtifactFile(session, file) {
|
|
1339
|
+
return sendMediaFileMessage(client, session.conversationId, file.path, '', {
|
|
1340
|
+
...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
|
|
1341
|
+
metadata: {
|
|
1342
|
+
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1343
|
+
// Media is permanent conversation content (promoted like a final
|
|
1344
|
+
// reply) but must never re-trigger other agents — the host's own
|
|
1345
|
+
// final text message stays the only turn-complete trigger.
|
|
1346
|
+
turnSemantics: 'turn_complete',
|
|
1347
|
+
replyBehavior: 'suppress_auto_reply',
|
|
1348
|
+
},
|
|
1349
|
+
});
|
|
1302
1350
|
}
|
|
1303
|
-
async function
|
|
1304
|
-
if (!
|
|
1351
|
+
async function routeWorkspaceGeneratedArtifacts(session, baseline) {
|
|
1352
|
+
if (!baseline)
|
|
1305
1353
|
return;
|
|
1306
1354
|
const logPrefix = `[canon-codex] [${session.conversationId.slice(0, 8)}]`;
|
|
1307
1355
|
try {
|
|
1308
|
-
const result = await
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
...(session.activeSelfContextId ? { selfContextId: session.activeSelfContextId } : {}),
|
|
1312
|
-
metadata: {
|
|
1313
|
-
...(session.currentTurnId ? { turnId: session.currentTurnId } : {}),
|
|
1314
|
-
// Media is permanent conversation content (promoted like a final
|
|
1315
|
-
// reply) but must never re-trigger other agents — the host's own
|
|
1316
|
-
// final text message stays the only turn-complete trigger.
|
|
1317
|
-
turnSemantics: 'turn_complete',
|
|
1318
|
-
replyBehavior: 'suppress_auto_reply',
|
|
1319
|
-
},
|
|
1320
|
-
}),
|
|
1356
|
+
const result = await collectTurnArtifacts({
|
|
1357
|
+
cwd: session.cwd,
|
|
1358
|
+
baseline,
|
|
1321
1359
|
});
|
|
1322
|
-
for (const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1360
|
+
for (const file of result.files) {
|
|
1361
|
+
try {
|
|
1362
|
+
const { messageId } = await sendTurnArtifactFile(session, file);
|
|
1363
|
+
console.error(`${logPrefix} Routed generated artifact ${file.relativePath ?? file.fileName} (${messageId})`);
|
|
1364
|
+
}
|
|
1365
|
+
catch (error) {
|
|
1366
|
+
console.error(`${logPrefix} Artifact upload failed for ${file.relativePath ?? file.fileName}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1367
|
+
}
|
|
1328
1368
|
}
|
|
1329
1369
|
for (const skipped of result.skipped) {
|
|
1330
|
-
if (skipped.reason === 'too-large' || skipped.reason === 'file-cap' || skipped.reason === '
|
|
1370
|
+
if (skipped.reason === 'too-large' || skipped.reason === 'file-cap' || skipped.reason === 'scan-limit') {
|
|
1331
1371
|
console.error(`${logPrefix} Artifact skipped ${skipped.fileName} (${skipped.reason})`);
|
|
1332
1372
|
}
|
|
1333
1373
|
}
|
|
@@ -1361,19 +1401,26 @@ export async function main() {
|
|
|
1361
1401
|
// Status-only seed: 'thinking' renders as a working filament row on the
|
|
1362
1402
|
// clients; text here would be bubbled as speech (v4 register rule).
|
|
1363
1403
|
writeCodexStreaming(session, '', 'thinking');
|
|
1364
|
-
let
|
|
1404
|
+
let artifactBaseline = null;
|
|
1365
1405
|
let artifactsRouted = false;
|
|
1366
1406
|
const routeArtifactsOnce = async () => {
|
|
1367
1407
|
if (artifactsRouted)
|
|
1368
1408
|
return;
|
|
1369
1409
|
artifactsRouted = true;
|
|
1370
|
-
|
|
1410
|
+
if (nextTurn.artifactRoutingMode === 'workspace-generated') {
|
|
1411
|
+
await routeWorkspaceGeneratedArtifacts(session, artifactBaseline);
|
|
1412
|
+
}
|
|
1371
1413
|
};
|
|
1372
1414
|
try {
|
|
1373
1415
|
const turnId = session.currentTurnId ?? randomUUID();
|
|
1374
1416
|
session.currentTurnId = turnId;
|
|
1375
|
-
|
|
1376
|
-
|
|
1417
|
+
let turnPrompt = nextTurn.prompt;
|
|
1418
|
+
if (nextTurn.artifactRoutingMode === 'workspace-generated') {
|
|
1419
|
+
artifactBaseline = await captureTurnArtifactSnapshot({ cwd: session.cwd }).catch((error) => {
|
|
1420
|
+
console.error(`[canon-codex] [${session.conversationId.slice(0, 8)}] Artifact snapshot failed:`, error instanceof Error ? error.message : error);
|
|
1421
|
+
return null;
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1377
1424
|
const modelGuard = buildCodexModelGuardMessage(session.state.model, codexCliStatus);
|
|
1378
1425
|
if (modelGuard) {
|
|
1379
1426
|
throw new ExecutionEnvironmentError(modelGuard, modelGuard);
|
|
@@ -1395,7 +1442,8 @@ export async function main() {
|
|
|
1395
1442
|
markTurnProgress(session);
|
|
1396
1443
|
writeTurn(session);
|
|
1397
1444
|
stopVisibleWorkSignal(session);
|
|
1398
|
-
|
|
1445
|
+
upsertCodexTextSegment(session, event);
|
|
1446
|
+
writeCodexStreaming(session, null, 'streaming');
|
|
1399
1447
|
return;
|
|
1400
1448
|
}
|
|
1401
1449
|
if (event.type === 'plan.updated') {
|
|
@@ -1824,7 +1872,7 @@ export async function main() {
|
|
|
1824
1872
|
{
|
|
1825
1873
|
id: 'mediaOut',
|
|
1826
1874
|
label: 'Media out',
|
|
1827
|
-
value: '
|
|
1875
|
+
value: 'Generated media artifacts',
|
|
1828
1876
|
},
|
|
1829
1877
|
],
|
|
1830
1878
|
execution: {
|
package/dist/turn-activity.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TurnOutputBlock } from '@canonmsg/core';
|
|
1
2
|
interface RunningCommandBlock {
|
|
2
3
|
command: string;
|
|
3
4
|
blockId: string;
|
|
@@ -7,7 +8,17 @@ export interface CommandBlockTracker {
|
|
|
7
8
|
sequence: number;
|
|
8
9
|
running: RunningCommandBlock[];
|
|
9
10
|
}
|
|
11
|
+
export interface TextSegmentBlockState {
|
|
12
|
+
turnLiveText: string;
|
|
13
|
+
turnBlocks: TurnOutputBlock[];
|
|
14
|
+
}
|
|
10
15
|
export declare function createCommandBlockTracker(): CommandBlockTracker;
|
|
16
|
+
export declare function applyTextSegmentBlock(state: TextSegmentBlockState, input: {
|
|
17
|
+
turnId: string | null | undefined;
|
|
18
|
+
itemId?: string;
|
|
19
|
+
text: string;
|
|
20
|
+
now?: number;
|
|
21
|
+
}): TextSegmentBlockState;
|
|
11
22
|
export declare function beginCommandBlock(tracker: CommandBlockTracker, input: {
|
|
12
23
|
turnId: string | null | undefined;
|
|
13
24
|
command: string;
|
package/dist/turn-activity.js
CHANGED
|
@@ -8,6 +8,49 @@ function normalizeOptionalString(value) {
|
|
|
8
8
|
const normalized = value?.trim();
|
|
9
9
|
return normalized ? normalized : undefined;
|
|
10
10
|
}
|
|
11
|
+
function textBlockId(turnId, itemId) {
|
|
12
|
+
return `message:${turnId ?? 'turn'}:${normalizeOptionalString(itemId) ?? 'latest'}`;
|
|
13
|
+
}
|
|
14
|
+
function buildLiveText(blocks) {
|
|
15
|
+
return blocks
|
|
16
|
+
.filter((block) => block.kind === 'text' && block.text?.trim())
|
|
17
|
+
.sort((left, right) => {
|
|
18
|
+
if (left.sequence !== right.sequence)
|
|
19
|
+
return left.sequence - right.sequence;
|
|
20
|
+
return left.id.localeCompare(right.id);
|
|
21
|
+
})
|
|
22
|
+
.map((block) => block.text)
|
|
23
|
+
.join('\n\n');
|
|
24
|
+
}
|
|
25
|
+
export function applyTextSegmentBlock(state, input) {
|
|
26
|
+
const id = textBlockId(input.turnId, input.itemId);
|
|
27
|
+
const now = input.now ?? Date.now();
|
|
28
|
+
const index = state.turnBlocks.findIndex((block) => block.id === id);
|
|
29
|
+
const existing = index >= 0 ? state.turnBlocks[index] : null;
|
|
30
|
+
const next = {
|
|
31
|
+
...(existing ?? {
|
|
32
|
+
sequence: state.turnBlocks.length + 1,
|
|
33
|
+
createdAt: now,
|
|
34
|
+
}),
|
|
35
|
+
id,
|
|
36
|
+
turnId: input.turnId ?? id,
|
|
37
|
+
kind: 'text',
|
|
38
|
+
status: existing?.status ?? 'running',
|
|
39
|
+
text: input.text,
|
|
40
|
+
updatedAt: now,
|
|
41
|
+
};
|
|
42
|
+
const turnBlocks = index >= 0
|
|
43
|
+
? [
|
|
44
|
+
...state.turnBlocks.slice(0, index),
|
|
45
|
+
next,
|
|
46
|
+
...state.turnBlocks.slice(index + 1),
|
|
47
|
+
]
|
|
48
|
+
: [...state.turnBlocks, next];
|
|
49
|
+
return {
|
|
50
|
+
turnBlocks,
|
|
51
|
+
turnLiveText: buildLiveText(turnBlocks) || state.turnLiveText,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
11
54
|
function nextCommandBlockId(tracker, turnId, itemId) {
|
|
12
55
|
const stableTurnId = turnId ?? 'turn';
|
|
13
56
|
if (itemId)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.6",
|
|
4
4
|
"description": "Canon host integration for Codex CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -29,9 +29,9 @@
|
|
|
29
29
|
"prepack": "npm run build"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@canonmsg/agent-sdk": "^3.
|
|
33
|
-
"@canonmsg/coding-agent-host": "^0.2.
|
|
34
|
-
"@canonmsg/core": "^2.
|
|
32
|
+
"@canonmsg/agent-sdk": "^3.3.1",
|
|
33
|
+
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
34
|
+
"@canonmsg/core": "^2.8.1"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|