@canonmsg/codex-plugin 0.18.12 → 0.19.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 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 packages/codex-plugin/dist/host.js --cwd /path/to/project --full-auto
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 packages/codex-plugin
152
+ cd adapters/codex-plugin
153
153
  npm install
154
154
  npm run build
155
155
  ```
@@ -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
+ }
@@ -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 RTDB `/control/{conversationId}/{agentId}` channel wiring.
2
+ * Codex host control-channel wiring (bridge notifications).
3
3
  *
4
- * Configures the shared `ControlChannelPoller` from `@canonmsg/core` to the
5
- * codex host characterization profile (the "codex host profile" describe
6
- * block in core's control-poller.test.ts is the contract):
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
- * - Keys: `session` + `signal`, plus `primitive` when a primitive handler is
9
- * configured; read sequentially per conversation, conversations polled one
10
- * at a time.
11
- * - Cadence: immediate first cycle, then active/idle delays + jitter with
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 { ControlChannelPoller, type ControlChannelRTDB, type ControlHandlerResult, type ControlPollerError, type ControlPrimitiveEvent, type ControlSessionEvent, type ControlSignalEvent } from '@canonmsg/core';
20
- export declare const CONTROL_POLL_MS = 2000;
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
- /** Scoped RTDB handle from `initRTDBAuth` — never the module-global default. */
25
- rtdb: ControlChannelRTDB;
26
- agentId: string;
27
- /** Live session conversation ids, snapshotted at the start of every cycle. */
28
- conversationIds: () => Iterable<string>;
29
- /** Sampled before each cycle to choose the active vs idle delay. */
30
- hasActiveWork: () => boolean;
31
- /**
32
- * Applies a newer `/session` control node. The node is always consumed
33
- * after the handler resolves; a thrown error leaves it in place (with
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
- export declare function createCodexControlPoller(input: CodexControlChannelInput): ControlChannelPoller;
31
+ /** Wire the codex control handlers to the bridge notifications; returns detach. */
32
+ export declare function attachCodexControlNotifications(input: CodexControlChannelInput): () => void;
@@ -1,56 +1,43 @@
1
1
  /**
2
- * Codex host RTDB `/control/{conversationId}/{agentId}` channel wiring.
2
+ * Codex host control-channel wiring (bridge notifications).
3
3
  *
4
- * Configures the shared `ControlChannelPoller` from `@canonmsg/core` to the
5
- * codex host characterization profile (the "codex host profile" describe
6
- * block in core's control-poller.test.ts is the contract):
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
- * - Keys: `session` + `signal`, plus `primitive` when a primitive handler is
9
- * configured; read sequentially per conversation, conversations polled one
10
- * at a time.
11
- * - Cadence: immediate first cycle, then active/idle delays + jitter with
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 { ControlChannelPoller, } from '@canonmsg/core';
20
- export const CONTROL_POLL_MS = 2_000;
21
- export const IDLE_CONTROL_POLL_MS = 10_000;
22
- export const CONTROL_POLL_JITTER_MS = 1_000;
23
- export function createCodexControlPoller(input) {
24
- return new ControlChannelPoller({
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;