@canonmsg/codex-plugin 0.20.0 → 0.22.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.
@@ -1,4 +1,4 @@
1
- import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core/contract';
1
+ import type { ApprovalNativeRequestMetadata, ApprovalRequestCategory, ApprovalRequestDetail, ApprovalResult, ApprovalRisk, CanonUnifiedDiff } from '@canonmsg/core';
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/contract';
1
+ import { sanitizeUnifiedDiffForSharing, truncateUnifiedDiff } from '@canonmsg/core';
2
2
  function isRecord(value) {
3
3
  return Boolean(value && typeof value === 'object' && !Array.isArray(value));
4
4
  }
@@ -1,2 +1,2 @@
1
- export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core/local';
2
- export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core/local';
1
+ export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
2
+ export type { CliMetadata, RunCliMetadataOptions, } from '@canonmsg/core';
package/dist/cli-entry.js CHANGED
@@ -1 +1 @@
1
- export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core/local';
1
+ export { handleCliMetadataRequest, isDirectExecution, readCliPackageVersion, runCli, } from '@canonmsg/core';
@@ -1,32 +1,52 @@
1
1
  /**
2
- * Codex host control-channel wiring (bridge notifications).
2
+ * Codex host RTDB `/control/{conversationId}/{agentId}` channel wiring.
3
3
  *
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.
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):
11
7
  *
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.
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.
16
18
  */
17
- import type { ControlPrimitiveEventRecord, ControlSessionEventRecord, ControlSignalEventRecord } from '@canonmsg/backend-contracts';
18
- import { type ControlNotificationSource, type HostControlHandlerResult } from '@canonmsg/agent-host';
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;
19
23
  export interface CodexControlChannelInput {
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;
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;
30
51
  }
31
- /** Wire the codex control handlers to the bridge notifications; returns detach. */
32
- export declare function attachCodexControlNotifications(input: CodexControlChannelInput): () => void;
52
+ export declare function createCodexControlPoller(input: CodexControlChannelInput): ControlChannelPoller;
@@ -1,43 +1,56 @@
1
1
  /**
2
- * Codex host control-channel wiring (bridge notifications).
2
+ * Codex host RTDB `/control/{conversationId}/{agentId}` channel wiring.
3
3
  *
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.
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):
11
7
  *
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.
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.
16
18
  */
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);
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 } : {}),
23
55
  });
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
- };
43
56
  }
@@ -0,0 +1,4 @@
1
+ export interface LongLivedStream {
2
+ start(): Promise<void>;
3
+ }
4
+ export declare function startCodexStreamInBackground(stream: LongLivedStream, onError: (error: unknown) => void): void;
@@ -0,0 +1,3 @@
1
+ export function startCodexStreamInBackground(stream, onError) {
2
+ stream.start().catch(onError);
3
+ }
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/contract';
2
+ import { type CanonRuntimeCommandDescriptor, type CanonRuntimeDescriptor, type CanonRuntimePresentationPolicy, type ExecutionEnvironmentMode, type WorkspaceOption, type CanonWorkspaceRootMetadata } from '@canonmsg/core';
3
3
  import { type CodexSkillMetadata } from './app-server-adapter.js';
4
4
  interface HostSessionState {
5
5
  lastError?: string;