@canonmsg/codex-plugin 0.18.11 → 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,5 @@
1
1
  import type { CodexApprovalPolicy, CodexEvent, CodexRunTurnOptions, CodexSandboxMode, CodexTurnResult } from './adapter.js';
2
+ export type JsonRecord = Record<string, unknown>;
2
3
  export interface CodexSkillMetadata {
3
4
  name: string;
4
5
  path: string;
@@ -18,6 +19,7 @@ export declare class CodexAppServerAdapter {
18
19
  private readonly configOverrides;
19
20
  private readonly fullAuto;
20
21
  private readonly bypassApprovalsAndSandbox;
22
+ private readonly dynamicTools;
21
23
  private child;
22
24
  private threadId;
23
25
  private loadedThreadId;
@@ -49,6 +51,7 @@ export declare class CodexAppServerAdapter {
49
51
  configOverrides?: string[];
50
52
  fullAuto?: boolean;
51
53
  bypassApprovalsAndSandbox?: boolean;
54
+ dynamicTools?: readonly JsonRecord[];
52
55
  });
53
56
  getThreadId(): string | null;
54
57
  clearThreadId(): void;
@@ -65,6 +68,7 @@ export declare class CodexAppServerAdapter {
65
68
  isRunning(): boolean;
66
69
  interrupt(): Promise<void>;
67
70
  compactThread(): Promise<void>;
71
+ requestAppServer(method: string, params: unknown): Promise<unknown>;
68
72
  close(): void;
69
73
  runTurn(prompt: string, onEvent: (event: CodexEvent) => void, onLog?: (line: string) => void, imagePaths?: readonly string[], _extraAddDirs?: readonly string[], options?: CodexRunTurnOptions): Promise<CodexTurnResult>;
70
74
  private resolveApprovalPolicy;
@@ -80,6 +84,7 @@ export declare class CodexAppServerAdapter {
80
84
  private handleLine;
81
85
  private handleServerRequest;
82
86
  private handleNotification;
87
+ private isCurrentThreadNotification;
83
88
  private resolveCurrentTurn;
84
89
  private clearActiveTurn;
85
90
  private sendRequest;
@@ -11,6 +11,7 @@ export class CodexAppServerAdapter {
11
11
  configOverrides;
12
12
  fullAuto;
13
13
  bypassApprovalsAndSandbox;
14
+ dynamicTools;
14
15
  child = null;
15
16
  threadId;
16
17
  loadedThreadId = null;
@@ -42,6 +43,7 @@ export class CodexAppServerAdapter {
42
43
  this.configOverrides = opts.configOverrides ?? [];
43
44
  this.fullAuto = opts.fullAuto ?? false;
44
45
  this.bypassApprovalsAndSandbox = opts.bypassApprovalsAndSandbox ?? false;
46
+ this.dynamicTools = opts.dynamicTools ?? [];
45
47
  }
46
48
  getThreadId() {
47
49
  return this.threadId;
@@ -92,6 +94,10 @@ export class CodexAppServerAdapter {
92
94
  threadId: this.threadId,
93
95
  });
94
96
  }
97
+ async requestAppServer(method, params) {
98
+ await this.ensureStarted();
99
+ return await this.sendRequest(method, params);
100
+ }
95
101
  close() {
96
102
  this.child?.kill('SIGTERM');
97
103
  this.child = null;
@@ -122,6 +128,7 @@ export class CodexAppServerAdapter {
122
128
  approvalPolicy: this.resolveApprovalPolicy(),
123
129
  excludeTurns: true,
124
130
  persistExtendedHistory: true,
131
+ ...(this.dynamicTools.length ? { dynamicTools: this.dynamicTools } : {}),
125
132
  });
126
133
  this.loadedThreadId = this.threadId;
127
134
  this.rememberResolvedModel(resumed);
@@ -133,6 +140,7 @@ export class CodexAppServerAdapter {
133
140
  ...(this.sandbox ? { sandbox: this.sandbox } : {}),
134
141
  ...this.configPayload(),
135
142
  approvalPolicy: this.resolveApprovalPolicy(),
143
+ ...(this.dynamicTools.length ? { dynamicTools: this.dynamicTools } : {}),
136
144
  experimentalRawEvents: false,
137
145
  persistExtendedHistory: true,
138
146
  });
@@ -358,16 +366,18 @@ export class CodexAppServerAdapter {
358
366
  }
359
367
  }
360
368
  handleNotification(method, params) {
361
- if (method === 'turn/started') {
362
- this.currentTurnId = readString(params.turn, 'id') ?? this.currentTurnId;
363
- this.currentOnEvent?.({ type: 'turn.started' });
364
- return;
365
- }
366
369
  if (method === 'skills/changed') {
367
370
  this.skillsCache = null;
368
371
  this.currentOnEvent?.({ type: 'skills.changed' });
369
372
  return;
370
373
  }
374
+ if (!this.isCurrentThreadNotification(params))
375
+ return;
376
+ if (method === 'turn/started') {
377
+ this.currentTurnId = readString(params.turn, 'id') ?? this.currentTurnId;
378
+ this.currentOnEvent?.({ type: 'turn.started' });
379
+ return;
380
+ }
371
381
  if (method === 'thread/status/changed') {
372
382
  const status = params.status;
373
383
  if (status?.type === 'active' && Array.isArray(status.activeFlags)) {
@@ -476,6 +486,15 @@ export class CodexAppServerAdapter {
476
486
  this.currentErrorText = stringifyPreview(params);
477
487
  }
478
488
  }
489
+ isCurrentThreadNotification(params) {
490
+ const threadId = readNotificationThreadId(params);
491
+ if (threadId && this.threadId && threadId !== this.threadId)
492
+ return false;
493
+ const turnId = readNotificationTurnId(params);
494
+ if (turnId && this.currentTurnId && turnId !== this.currentTurnId)
495
+ return false;
496
+ return true;
497
+ }
479
498
  resolveCurrentTurn() {
480
499
  const result = {
481
500
  threadId: this.threadId,
@@ -538,6 +557,18 @@ function readRawString(record, key) {
538
557
  const value = record?.[key];
539
558
  return typeof value === 'string' ? value : undefined;
540
559
  }
560
+ function readNotificationThreadId(params) {
561
+ return readString(params, 'threadId')
562
+ ?? readString(params.thread, 'id')
563
+ ?? readString(params.turn, 'threadId')
564
+ ?? readString(params.item, 'threadId')
565
+ ?? readString(params.status, 'threadId');
566
+ }
567
+ function readNotificationTurnId(params) {
568
+ return readString(params, 'turnId')
569
+ ?? readString(params.turn, 'id')
570
+ ?? readString(params.item, 'turnId');
571
+ }
541
572
  function readNullableString(record, key) {
542
573
  const value = record?.[key];
543
574
  return typeof value === 'string' && value.trim() ? value.trim() : undefined;
@@ -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';
@@ -0,0 +1,42 @@
1
+ type JsonRecord = Record<string, unknown>;
2
+ interface DynamicToolSpec {
3
+ [key: string]: unknown;
4
+ namespace: 'codex_app';
5
+ name: string;
6
+ description: string;
7
+ inputSchema: JsonRecord;
8
+ deferLoading?: boolean;
9
+ }
10
+ export interface CodexAppToolAdapter {
11
+ requestAppServer(method: string, params: unknown): Promise<unknown>;
12
+ }
13
+ export interface CodexAppToolWorkspace {
14
+ id: string;
15
+ label: string;
16
+ cwd: string;
17
+ }
18
+ export interface CodexAppToolRuntime {
19
+ adapter: CodexAppToolAdapter;
20
+ currentThreadId: string | null;
21
+ currentCwd: string;
22
+ workspaces: ReadonlyArray<CodexAppToolWorkspace>;
23
+ model?: string | null;
24
+ effort?: string | null;
25
+ }
26
+ export interface CodexAppToolCallParams {
27
+ arguments?: unknown;
28
+ namespace?: unknown;
29
+ tool?: unknown;
30
+ }
31
+ type DynamicToolCallResponse = {
32
+ success: boolean;
33
+ contentItems: Array<{
34
+ type: 'inputText';
35
+ text: string;
36
+ }>;
37
+ };
38
+ export declare const CODEX_APP_DYNAMIC_TOOLS: ReadonlyArray<DynamicToolSpec>;
39
+ export declare function isCodexAppToolCall(params: Record<string, unknown>): boolean;
40
+ export declare function deniedCodexAppToolResult(reason: string): DynamicToolCallResponse;
41
+ export declare function handleCodexAppToolCall(runtime: CodexAppToolRuntime, params: CodexAppToolCallParams): Promise<DynamicToolCallResponse>;
42
+ export {};