@canonmsg/codex-plugin 0.18.12 → 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/README.md +2 -2
- package/dist/app-server-adapter.d.ts +7 -0
- package/dist/app-server-adapter.js +15 -2
- package/dist/app-server-approval.d.ts +1 -1
- package/dist/app-server-approval.js +1 -1
- package/dist/bridge-bin.d.ts +14 -0
- package/dist/bridge-bin.js +27 -0
- package/dist/cli-entry.d.ts +2 -2
- package/dist/cli-entry.js +1 -1
- package/dist/control-channel.d.ts +26 -46
- package/dist/control-channel.js +37 -50
- package/dist/host.d.ts +1 -1
- package/dist/host.js +842 -932
- package/dist/register.js +73 -25
- package/dist/session-store.d.ts +1 -1
- package/dist/session-store.js +1 -1
- package/dist/turn-activity.d.ts +16 -11
- package/dist/turn-activity.js +17 -41
- package/package.json +10 -7
- package/dist/host-lifecycle.d.ts +0 -4
- package/dist/host-lifecycle.js +0 -3
- package/dist/inbound-policy.d.ts +0 -22
- package/dist/inbound-policy.js +0 -46
- package/dist/startup-recovery.d.ts +0 -46
- package/dist/startup-recovery.js +0 -59
package/README.md
CHANGED
|
@@ -108,7 +108,7 @@ ps aux | rg canon-codex
|
|
|
108
108
|
If you installed the package only inside this repo and not globally, run the built host directly:
|
|
109
109
|
|
|
110
110
|
```bash
|
|
111
|
-
node
|
|
111
|
+
node adapters/codex-plugin/dist/host.js --cwd /path/to/project --full-auto
|
|
112
112
|
```
|
|
113
113
|
|
|
114
114
|
If `canon-codex` starts but cannot find the `codex` binary, either fix your `PATH` or launch with an explicit binary path:
|
|
@@ -149,7 +149,7 @@ CANON_AGENT=frontend canon-codex --cwd ~/projects/frontend
|
|
|
149
149
|
## Development
|
|
150
150
|
|
|
151
151
|
```bash
|
|
152
|
-
cd
|
|
152
|
+
cd adapters/codex-plugin
|
|
153
153
|
npm install
|
|
154
154
|
npm run build
|
|
155
155
|
```
|
|
@@ -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)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core';
|
|
1
|
+
import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core/contract';
|
|
2
2
|
/**
|
|
3
3
|
* Mapping for Codex app-server native approval requests.
|
|
4
4
|
*
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { sanitizeUnifiedDiffForSharing, truncateUnifiedDiff } from '@canonmsg/core';
|
|
1
|
+
import { sanitizeUnifiedDiffForSharing, truncateUnifiedDiff } from '@canonmsg/core/contract';
|
|
2
2
|
function isRecord(value) {
|
|
3
3
|
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
4
4
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packaged canon-bridge daemon resolution (bridge plan Phase 6, §F3).
|
|
3
|
+
*
|
|
4
|
+
* On a fresh npm install nothing has populated ~/.canon/bin yet, so
|
|
5
|
+
* connectBridge()'s fallback chain (explicit binPath → $CANON_BRIDGE_BIN →
|
|
6
|
+
* the shared install) has nothing to spawn. The plugin therefore ships the
|
|
7
|
+
* daemon as its own @canonmsg/bridge dependency and passes the packaged
|
|
8
|
+
* dist entry as binPath.
|
|
9
|
+
*
|
|
10
|
+
* $CANON_BRIDGE_BIN still wins: when the env override is set we return
|
|
11
|
+
* undefined and let connectBridge()'s own env resolution take it (an
|
|
12
|
+
* options.binPath would out-rank the env var otherwise).
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolvePackagedBridgeBin(env?: NodeJS.ProcessEnv): string | undefined;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packaged canon-bridge daemon resolution (bridge plan Phase 6, §F3).
|
|
3
|
+
*
|
|
4
|
+
* On a fresh npm install nothing has populated ~/.canon/bin yet, so
|
|
5
|
+
* connectBridge()'s fallback chain (explicit binPath → $CANON_BRIDGE_BIN →
|
|
6
|
+
* the shared install) has nothing to spawn. The plugin therefore ships the
|
|
7
|
+
* daemon as its own @canonmsg/bridge dependency and passes the packaged
|
|
8
|
+
* dist entry as binPath.
|
|
9
|
+
*
|
|
10
|
+
* $CANON_BRIDGE_BIN still wins: when the env override is set we return
|
|
11
|
+
* undefined and let connectBridge()'s own env resolution take it (an
|
|
12
|
+
* options.binPath would out-rank the env var otherwise).
|
|
13
|
+
*/
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
import { dirname, join as joinPath } from 'node:path';
|
|
16
|
+
export function resolvePackagedBridgeBin(env = process.env) {
|
|
17
|
+
if (env.CANON_BRIDGE_BIN)
|
|
18
|
+
return undefined;
|
|
19
|
+
try {
|
|
20
|
+
const require = createRequire(import.meta.url);
|
|
21
|
+
const manifestPath = require.resolve('@canonmsg/bridge/package.json');
|
|
22
|
+
return joinPath(dirname(manifestPath), 'dist', 'main.js');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/cli-entry.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
|
|
2
|
-
export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core';
|
|
1
|
+
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core/local';
|
|
2
|
+
export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core/local';
|
package/dist/cli-entry.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
|
|
1
|
+
export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core/local';
|
|
@@ -1,52 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codex host
|
|
2
|
+
* Codex host control-channel wiring (bridge notifications).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Phase 6: the host-side RTDB `/control` poller is DELETED — the Bridge is
|
|
5
|
+
* the single authoritative consumer for this identity (one adaptive poller
|
|
6
|
+
* per daemon; cadence, jitter, baselines and consume semantics live there)
|
|
7
|
+
* and pushes `onSessionControl` / `onControlSignal` / `onControlPrimitive`
|
|
8
|
+
* notifications instead. This module keeps the codex-shaped handler wiring:
|
|
9
|
+
* session + signal ride the shared agent-host router; the primitive channel
|
|
10
|
+
* (context.compact) subscribes alongside them.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* the activity probe sampled BEFORE the cycle runs.
|
|
13
|
-
* - Session controls are always consumed once newer (default consume), even
|
|
14
|
-
* when no live session applied them; signals are NOT consumed when the
|
|
15
|
-
* handler throws (consumeOnError stays false).
|
|
16
|
-
* - Dedupe is primed eagerly via `poller.baseline([conversationId])` at
|
|
17
|
-
* session creation — the second legacy poll site.
|
|
12
|
+
* Consume semantics note (deliberate Phase-6 change, plan §2): the node is
|
|
13
|
+
* consumed bridge-side BEFORE dispatch, so handler return values are
|
|
14
|
+
* advisory-only and a throwing handler is logged, never retried — the legacy
|
|
15
|
+
* leave-in-place-on-error behavior is gone with the host poller.
|
|
18
16
|
*/
|
|
19
|
-
import
|
|
20
|
-
|
|
21
|
-
export declare const IDLE_CONTROL_POLL_MS = 10000;
|
|
22
|
-
export declare const CONTROL_POLL_JITTER_MS = 1000;
|
|
17
|
+
import type { ControlPrimitiveEventRecord, ControlSessionEventRecord, ControlSignalEventRecord } from '@canonmsg/backend-contracts';
|
|
18
|
+
import { type ControlNotificationSource, type HostControlHandlerResult } from '@canonmsg/agent-host';
|
|
23
19
|
export interface CodexControlChannelInput {
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
* dedupe already advanced, so it is never retried).
|
|
35
|
-
*/
|
|
36
|
-
onSessionControl: (event: ControlSessionEvent) => Promise<ControlHandlerResult> | ControlHandlerResult;
|
|
37
|
-
/**
|
|
38
|
-
* Handles a newer `/signal` node. Return `{ consume: false }` to leave the
|
|
39
|
-
* node in place; thrown errors also leave it (consumeOnError stays false).
|
|
40
|
-
*/
|
|
41
|
-
onSignal: (event: ControlSignalEvent) => Promise<ControlHandlerResult> | ControlHandlerResult;
|
|
42
|
-
/**
|
|
43
|
-
* Handles a newer `/primitive` node. Primitive commands are consumed by
|
|
44
|
-
* default after the handler resolves or throws, matching core's command
|
|
45
|
-
* semantics so one failed command cannot replay forever.
|
|
46
|
-
*/
|
|
47
|
-
onPrimitive?: (event: ControlPrimitiveEvent) => Promise<ControlHandlerResult> | ControlHandlerResult;
|
|
48
|
-
onError?: (error: ControlPollerError) => void;
|
|
49
|
-
/** Injectable jitter source (tests). */
|
|
50
|
-
random?: () => number;
|
|
20
|
+
/** Buffered notification source (registered before the hello completes). */
|
|
21
|
+
source: ControlNotificationSource;
|
|
22
|
+
/** Applies a `/session` control (model/effort; permission is start-only). */
|
|
23
|
+
onSessionControl: (event: ControlSessionEventRecord) => Promise<HostControlHandlerResult> | HostControlHandlerResult;
|
|
24
|
+
/** Handles a `/signal` node (interrupt / stop_and_drop / new_session). */
|
|
25
|
+
onSignal: (event: ControlSignalEventRecord) => Promise<HostControlHandlerResult> | HostControlHandlerResult;
|
|
26
|
+
/** Handles a `/primitive` command (context.compact). */
|
|
27
|
+
onPrimitive?: (event: ControlPrimitiveEventRecord) => Promise<HostControlHandlerResult> | HostControlHandlerResult;
|
|
28
|
+
/** Error sink for throwing handlers (default: console.error). */
|
|
29
|
+
onError?: (error: unknown, kind: 'session' | 'signal' | 'primitive', conversationId: string | null) => void;
|
|
51
30
|
}
|
|
52
|
-
|
|
31
|
+
/** Wire the codex control handlers to the bridge notifications; returns detach. */
|
|
32
|
+
export declare function attachCodexControlNotifications(input: CodexControlChannelInput): () => void;
|
package/dist/control-channel.js
CHANGED
|
@@ -1,56 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Codex host
|
|
2
|
+
* Codex host control-channel wiring (bridge notifications).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Phase 6: the host-side RTDB `/control` poller is DELETED — the Bridge is
|
|
5
|
+
* the single authoritative consumer for this identity (one adaptive poller
|
|
6
|
+
* per daemon; cadence, jitter, baselines and consume semantics live there)
|
|
7
|
+
* and pushes `onSessionControl` / `onControlSignal` / `onControlPrimitive`
|
|
8
|
+
* notifications instead. This module keeps the codex-shaped handler wiring:
|
|
9
|
+
* session + signal ride the shared agent-host router; the primitive channel
|
|
10
|
+
* (context.compact) subscribes alongside them.
|
|
7
11
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* -
|
|
12
|
-
* the activity probe sampled BEFORE the cycle runs.
|
|
13
|
-
* - Session controls are always consumed once newer (default consume), even
|
|
14
|
-
* when no live session applied them; signals are NOT consumed when the
|
|
15
|
-
* handler throws (consumeOnError stays false).
|
|
16
|
-
* - Dedupe is primed eagerly via `poller.baseline([conversationId])` at
|
|
17
|
-
* session creation — the second legacy poll site.
|
|
12
|
+
* Consume semantics note (deliberate Phase-6 change, plan §2): the node is
|
|
13
|
+
* consumed bridge-side BEFORE dispatch, so handler return values are
|
|
14
|
+
* advisory-only and a throwing handler is logged, never retried — the legacy
|
|
15
|
+
* leave-in-place-on-error behavior is gone with the host poller.
|
|
18
16
|
*/
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
rtdb: input.rtdb,
|
|
26
|
-
agentId: input.agentId,
|
|
27
|
-
conversationIds: input.conversationIds,
|
|
28
|
-
cadence: {
|
|
29
|
-
kind: 'adaptive',
|
|
30
|
-
activeMs: CONTROL_POLL_MS,
|
|
31
|
-
idleMs: IDLE_CONTROL_POLL_MS,
|
|
32
|
-
jitterMs: CONTROL_POLL_JITTER_MS,
|
|
33
|
-
hasActiveWork: input.hasActiveWork,
|
|
34
|
-
activitySample: 'cycle-start',
|
|
35
|
-
},
|
|
36
|
-
pollOnStart: true,
|
|
37
|
-
conversationConcurrency: 'sequential',
|
|
38
|
-
handlers: {
|
|
39
|
-
session: {
|
|
40
|
-
handle: input.onSessionControl,
|
|
41
|
-
},
|
|
42
|
-
signal: {
|
|
43
|
-
handle: input.onSignal,
|
|
44
|
-
},
|
|
45
|
-
...(input.onPrimitive
|
|
46
|
-
? {
|
|
47
|
-
primitive: {
|
|
48
|
-
handle: input.onPrimitive,
|
|
49
|
-
},
|
|
50
|
-
}
|
|
51
|
-
: {}),
|
|
52
|
-
},
|
|
53
|
-
...(input.onError ? { onError: input.onError } : {}),
|
|
54
|
-
...(input.random ? { random: input.random } : {}),
|
|
17
|
+
import { attachHostControlNotifications, } from '@canonmsg/agent-host';
|
|
18
|
+
/** Wire the codex control handlers to the bridge notifications; returns detach. */
|
|
19
|
+
export function attachCodexControlNotifications(input) {
|
|
20
|
+
const report = input.onError ?? ((error, kind, conversationId) => {
|
|
21
|
+
const convo = conversationId ? ` [${conversationId.slice(0, 8)}]` : '';
|
|
22
|
+
console.error(`[canon-codex]${convo} Control ${kind} handler error:`, error);
|
|
55
23
|
});
|
|
24
|
+
const detachShared = attachHostControlNotifications(input.source, { logPrefix: '[canon-codex]' }, {
|
|
25
|
+
onSessionControl: input.onSessionControl,
|
|
26
|
+
onControlSignal: input.onSignal,
|
|
27
|
+
onError: (error, kind, conversationId) => report(error, kind, conversationId),
|
|
28
|
+
});
|
|
29
|
+
let detachPrimitive = null;
|
|
30
|
+
if (input.onPrimitive) {
|
|
31
|
+
const onPrimitive = input.onPrimitive;
|
|
32
|
+
detachPrimitive = input.source.onNotification('onControlPrimitive', (params) => {
|
|
33
|
+
const event = params;
|
|
34
|
+
void Promise.resolve(onPrimitive(event)).catch((error) => {
|
|
35
|
+
report(error, 'primitive', event?.conversationId ?? null);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return () => {
|
|
40
|
+
detachShared();
|
|
41
|
+
detachPrimitive?.();
|
|
42
|
+
};
|
|
56
43
|
}
|
package/dist/host.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type WorkspaceOption, type CanonWorkspaceRootMetadata } from '@canonmsg/core';
|
|
2
|
+
import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type WorkspaceOption, type CanonWorkspaceRootMetadata } from '@canonmsg/core/contract';
|
|
3
3
|
import { type CodexSkillMetadata } from './app-server-adapter.js';
|
|
4
4
|
interface HostSessionState {
|
|
5
5
|
lastError?: string;
|