@canonmsg/codex-plugin 0.18.11 → 0.18.12
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/app-server-adapter.d.ts +5 -0
- package/dist/app-server-adapter.js +36 -5
- package/dist/codex-app-tools.d.ts +42 -0
- package/dist/codex-app-tools.js +519 -0
- package/dist/host.js +27 -4
- package/dist/session-store.js +1 -1
- package/package.json +3 -3
|
@@ -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;
|
|
@@ -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 {};
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
const emptyObjectSchema = {
|
|
2
|
+
type: 'object',
|
|
3
|
+
properties: {},
|
|
4
|
+
additionalProperties: false,
|
|
5
|
+
};
|
|
6
|
+
const modelProperties = {
|
|
7
|
+
model: {
|
|
8
|
+
type: 'string',
|
|
9
|
+
description: 'Optional model override. Omit unless the user explicitly asks for a specific model.',
|
|
10
|
+
},
|
|
11
|
+
thinking: {
|
|
12
|
+
type: 'string',
|
|
13
|
+
description: 'Optional reasoning effort override.',
|
|
14
|
+
enum: ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const createThreadTargetSchema = {
|
|
18
|
+
description: 'Where to create the thread. Canon currently supports local project targets only.',
|
|
19
|
+
anyOf: [
|
|
20
|
+
{
|
|
21
|
+
type: 'object',
|
|
22
|
+
additionalProperties: false,
|
|
23
|
+
properties: {
|
|
24
|
+
type: { type: 'string', enum: ['project'] },
|
|
25
|
+
projectId: { type: 'string', description: 'Canon workspace id.' },
|
|
26
|
+
environment: {
|
|
27
|
+
anyOf: [
|
|
28
|
+
{
|
|
29
|
+
type: 'object',
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
properties: {
|
|
32
|
+
type: { type: 'string', enum: ['local'] },
|
|
33
|
+
},
|
|
34
|
+
required: ['type'],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'object',
|
|
38
|
+
additionalProperties: true,
|
|
39
|
+
properties: {
|
|
40
|
+
type: { type: 'string', enum: ['worktree'] },
|
|
41
|
+
},
|
|
42
|
+
required: ['type'],
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
required: ['type', 'projectId', 'environment'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'object',
|
|
51
|
+
additionalProperties: false,
|
|
52
|
+
properties: {
|
|
53
|
+
type: { type: 'string', enum: ['projectless'] },
|
|
54
|
+
directoryName: { type: 'string' },
|
|
55
|
+
},
|
|
56
|
+
required: ['type'],
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
};
|
|
60
|
+
const forkEnvironmentSchema = {
|
|
61
|
+
description: 'Where the fork should run. Canon currently supports same-directory forks only.',
|
|
62
|
+
anyOf: [
|
|
63
|
+
{
|
|
64
|
+
type: 'object',
|
|
65
|
+
additionalProperties: false,
|
|
66
|
+
properties: {
|
|
67
|
+
type: { type: 'string', enum: ['same-directory'] },
|
|
68
|
+
},
|
|
69
|
+
required: ['type'],
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'object',
|
|
73
|
+
additionalProperties: true,
|
|
74
|
+
properties: {
|
|
75
|
+
type: { type: 'string', enum: ['worktree'] },
|
|
76
|
+
},
|
|
77
|
+
required: ['type'],
|
|
78
|
+
},
|
|
79
|
+
],
|
|
80
|
+
};
|
|
81
|
+
function tool(name, description, inputSchema, deferLoading = true) {
|
|
82
|
+
return { namespace: 'codex_app', name, description, inputSchema, deferLoading };
|
|
83
|
+
}
|
|
84
|
+
export const CODEX_APP_DYNAMIC_TOOLS = [
|
|
85
|
+
tool('automation_update', 'Create, update, view, or delete Codex app automations. Canon exposes the name for compatibility, but does not manage Desktop automations.', {
|
|
86
|
+
type: 'object',
|
|
87
|
+
additionalProperties: false,
|
|
88
|
+
properties: {
|
|
89
|
+
id: { type: 'string' },
|
|
90
|
+
mode: { type: 'string' },
|
|
91
|
+
kind: { type: 'string' },
|
|
92
|
+
name: { type: 'string' },
|
|
93
|
+
prompt: { type: 'string' },
|
|
94
|
+
rrule: { type: 'string' },
|
|
95
|
+
cwds: {
|
|
96
|
+
anyOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
97
|
+
},
|
|
98
|
+
destination: { type: 'string' },
|
|
99
|
+
executionEnvironment: { type: 'string' },
|
|
100
|
+
localEnvironmentConfigPath: { type: ['string', 'null'] },
|
|
101
|
+
model: { type: 'string' },
|
|
102
|
+
reasoningEffort: { type: 'string' },
|
|
103
|
+
targetThreadId: { type: 'string' },
|
|
104
|
+
status: { type: 'string' },
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
tool('navigate_to_codex_page', 'Navigate the Codex Desktop UI. Canon exposes the name for compatibility, but has no Codex Desktop page to navigate.', { type: 'object', additionalProperties: true, properties: {} }),
|
|
108
|
+
tool('read_thread_terminal', 'Read the Codex Desktop terminal output for this thread. Canon exposes the name for compatibility, but has no Desktop terminal pane.', emptyObjectSchema, false),
|
|
109
|
+
tool('load_workspace_dependencies', 'Locate bundled Desktop workspace dependency runtimes. Canon exposes the name for compatibility, but does not provide Desktop bundle paths.', emptyObjectSchema, false),
|
|
110
|
+
tool('fork_thread', 'Fork a Codex thread. Omit threadId to fork the calling thread. Canon supports same-directory forks.', {
|
|
111
|
+
type: 'object',
|
|
112
|
+
additionalProperties: false,
|
|
113
|
+
properties: {
|
|
114
|
+
threadId: { type: 'string' },
|
|
115
|
+
environment: forkEnvironmentSchema,
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
tool('handoff_thread', 'Move a Codex thread between a checkout and worktree. Canon exposes the name for compatibility, but does not manage Desktop handoffs.', {
|
|
119
|
+
type: 'object',
|
|
120
|
+
additionalProperties: false,
|
|
121
|
+
properties: { threadId: { type: 'string' } },
|
|
122
|
+
required: ['threadId'],
|
|
123
|
+
}),
|
|
124
|
+
tool('get_handoff_status', 'Read Codex Desktop handoff status. Canon exposes the name for compatibility, but does not manage Desktop handoffs.', {
|
|
125
|
+
type: 'object',
|
|
126
|
+
additionalProperties: false,
|
|
127
|
+
properties: { threadId: { type: 'string' } },
|
|
128
|
+
}),
|
|
129
|
+
tool('list_projects', 'List Canon workspaces available to Codex app tools.', emptyObjectSchema),
|
|
130
|
+
tool('create_thread', 'Create a separate Codex thread only when the user explicitly asks for a new or separate thread. Canon supports local project targets.', {
|
|
131
|
+
type: 'object',
|
|
132
|
+
additionalProperties: false,
|
|
133
|
+
properties: {
|
|
134
|
+
prompt: { type: 'string', description: 'Initial prompt for the new thread.' },
|
|
135
|
+
target: createThreadTargetSchema,
|
|
136
|
+
...modelProperties,
|
|
137
|
+
},
|
|
138
|
+
required: ['prompt'],
|
|
139
|
+
}),
|
|
140
|
+
tool('list_threads', 'List recent Codex threads in Canon-configured workspaces.', {
|
|
141
|
+
type: 'object',
|
|
142
|
+
additionalProperties: false,
|
|
143
|
+
properties: {
|
|
144
|
+
query: { type: 'string' },
|
|
145
|
+
limit: { type: 'number' },
|
|
146
|
+
archived: { type: 'boolean' },
|
|
147
|
+
},
|
|
148
|
+
}),
|
|
149
|
+
tool('read_thread', 'Read recent status and turn summaries for one Codex thread without opening it.', {
|
|
150
|
+
type: 'object',
|
|
151
|
+
additionalProperties: false,
|
|
152
|
+
properties: {
|
|
153
|
+
threadId: { type: 'string' },
|
|
154
|
+
},
|
|
155
|
+
required: ['threadId'],
|
|
156
|
+
}),
|
|
157
|
+
tool('send_message_to_thread', 'Send a follow-up prompt to an existing Codex thread in the background.', {
|
|
158
|
+
type: 'object',
|
|
159
|
+
additionalProperties: false,
|
|
160
|
+
properties: {
|
|
161
|
+
threadId: { type: 'string' },
|
|
162
|
+
prompt: { type: 'string' },
|
|
163
|
+
...modelProperties,
|
|
164
|
+
},
|
|
165
|
+
required: ['threadId', 'prompt'],
|
|
166
|
+
}),
|
|
167
|
+
tool('set_thread_pinned', 'Pin or unpin a Codex thread. Canon exposes the name for compatibility, but pinned state is Desktop-only.', {
|
|
168
|
+
type: 'object',
|
|
169
|
+
additionalProperties: false,
|
|
170
|
+
properties: {
|
|
171
|
+
threadId: { type: 'string' },
|
|
172
|
+
pinned: { type: 'boolean' },
|
|
173
|
+
},
|
|
174
|
+
required: ['threadId', 'pinned'],
|
|
175
|
+
}),
|
|
176
|
+
tool('set_thread_archived', 'Archive or unarchive a Codex thread.', {
|
|
177
|
+
type: 'object',
|
|
178
|
+
additionalProperties: false,
|
|
179
|
+
properties: {
|
|
180
|
+
threadId: { type: 'string' },
|
|
181
|
+
archived: { type: 'boolean' },
|
|
182
|
+
},
|
|
183
|
+
required: ['archived'],
|
|
184
|
+
}),
|
|
185
|
+
tool('set_thread_title', 'Rename a Codex thread.', {
|
|
186
|
+
type: 'object',
|
|
187
|
+
additionalProperties: false,
|
|
188
|
+
properties: {
|
|
189
|
+
threadId: { type: 'string' },
|
|
190
|
+
title: { type: 'string' },
|
|
191
|
+
},
|
|
192
|
+
required: ['threadId', 'title'],
|
|
193
|
+
}),
|
|
194
|
+
];
|
|
195
|
+
const CODEX_APP_TOOL_NAMES = new Set(CODEX_APP_DYNAMIC_TOOLS.map((entry) => String(entry.name)));
|
|
196
|
+
const UNSUPPORTED_TOOLS = new Map([
|
|
197
|
+
['automation_update', 'Canon does not manage Codex Desktop automations.'],
|
|
198
|
+
['navigate_to_codex_page', 'Canon has no Codex Desktop page to navigate.'],
|
|
199
|
+
['read_thread_terminal', 'Canon has no Codex Desktop terminal pane to read.'],
|
|
200
|
+
['load_workspace_dependencies', 'Canon does not provide Codex Desktop bundled dependency paths.'],
|
|
201
|
+
['handoff_thread', 'Canon does not manage Codex Desktop handoffs.'],
|
|
202
|
+
['get_handoff_status', 'Canon does not manage Codex Desktop handoffs.'],
|
|
203
|
+
['set_thread_pinned', 'Pinned thread state is Codex Desktop-only.'],
|
|
204
|
+
]);
|
|
205
|
+
export function isCodexAppToolCall(params) {
|
|
206
|
+
const namespace = typeof params.namespace === 'string' ? params.namespace : null;
|
|
207
|
+
const rawTool = typeof params.tool === 'string' ? params.tool.trim() : '';
|
|
208
|
+
const toolName = normalizeToolName(params.tool);
|
|
209
|
+
if (namespace && namespace !== 'codex_app')
|
|
210
|
+
return rawTool.startsWith('codex_app.');
|
|
211
|
+
return namespace === 'codex_app'
|
|
212
|
+
|| rawTool.startsWith('codex_app.')
|
|
213
|
+
|| (toolName ? CODEX_APP_TOOL_NAMES.has(toolName) : false);
|
|
214
|
+
}
|
|
215
|
+
export function deniedCodexAppToolResult(reason) {
|
|
216
|
+
return toolResult(false, { error: reason });
|
|
217
|
+
}
|
|
218
|
+
export async function handleCodexAppToolCall(runtime, params) {
|
|
219
|
+
const toolName = normalizeToolName(params.tool);
|
|
220
|
+
if (!toolName || !CODEX_APP_TOOL_NAMES.has(toolName)) {
|
|
221
|
+
return toolResult(false, { error: `Unsupported codex_app tool: ${String(params.tool ?? 'unknown')}` });
|
|
222
|
+
}
|
|
223
|
+
const unsupportedReason = UNSUPPORTED_TOOLS.get(toolName);
|
|
224
|
+
if (unsupportedReason) {
|
|
225
|
+
return toolResult(false, { tool: toolName, error: unsupportedReason });
|
|
226
|
+
}
|
|
227
|
+
const args = parseToolArguments(params.arguments);
|
|
228
|
+
try {
|
|
229
|
+
switch (toolName) {
|
|
230
|
+
case 'list_projects':
|
|
231
|
+
return toolResult(true, {
|
|
232
|
+
projects: listToolWorkspaces(runtime).map((workspace) => ({
|
|
233
|
+
id: workspace.id,
|
|
234
|
+
label: workspace.label,
|
|
235
|
+
cwd: workspace.cwd,
|
|
236
|
+
environments: ['local'],
|
|
237
|
+
})),
|
|
238
|
+
unsupported: {
|
|
239
|
+
worktreeEnvironments: 'Canon host app-server tools only create local project threads.',
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
case 'create_thread':
|
|
243
|
+
return await createThread(runtime, args);
|
|
244
|
+
case 'fork_thread':
|
|
245
|
+
return await forkThread(runtime, args);
|
|
246
|
+
case 'list_threads':
|
|
247
|
+
return await listThreads(runtime, args);
|
|
248
|
+
case 'read_thread':
|
|
249
|
+
return await readThread(runtime, args);
|
|
250
|
+
case 'send_message_to_thread':
|
|
251
|
+
return await sendMessageToThread(runtime, args);
|
|
252
|
+
case 'set_thread_archived':
|
|
253
|
+
return await setThreadArchived(runtime, args);
|
|
254
|
+
case 'set_thread_title':
|
|
255
|
+
return await setThreadTitle(runtime, args);
|
|
256
|
+
default:
|
|
257
|
+
return toolResult(false, { tool: toolName, error: 'Tool is registered but has no Canon handler.' });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
catch (error) {
|
|
261
|
+
return toolResult(false, {
|
|
262
|
+
tool: toolName,
|
|
263
|
+
error: error instanceof Error ? error.message : String(error),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
async function createThread(runtime, args) {
|
|
268
|
+
const prompt = readString(args, 'prompt');
|
|
269
|
+
if (!prompt)
|
|
270
|
+
throw new Error('create_thread requires a non-empty prompt.');
|
|
271
|
+
const cwd = resolveCreateThreadCwd(runtime, args);
|
|
272
|
+
const started = await runtime.adapter.requestAppServer('thread/start', {
|
|
273
|
+
cwd,
|
|
274
|
+
...(readString(args, 'model') ?? runtime.model ? { model: readString(args, 'model') ?? runtime.model } : {}),
|
|
275
|
+
dynamicTools: CODEX_APP_DYNAMIC_TOOLS,
|
|
276
|
+
experimentalRawEvents: false,
|
|
277
|
+
persistExtendedHistory: true,
|
|
278
|
+
});
|
|
279
|
+
const threadId = readThreadId(started);
|
|
280
|
+
if (!threadId)
|
|
281
|
+
throw new Error('Codex app-server did not return a thread id for the new thread.');
|
|
282
|
+
const turnStarted = await runtime.adapter.requestAppServer('turn/start', buildTurnStartParams({
|
|
283
|
+
threadId,
|
|
284
|
+
prompt,
|
|
285
|
+
model: readString(args, 'model') ?? null,
|
|
286
|
+
effort: readString(args, 'thinking') ?? readString(args, 'effort') ?? runtime.effort ?? null,
|
|
287
|
+
}));
|
|
288
|
+
return toolResult(true, {
|
|
289
|
+
threadId,
|
|
290
|
+
turnId: readTurnId(turnStarted) ?? null,
|
|
291
|
+
status: 'submitted',
|
|
292
|
+
cwd,
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
async function forkThread(runtime, args) {
|
|
296
|
+
const environment = isRecord(args.environment) ? args.environment : null;
|
|
297
|
+
const environmentType = readString(environment, 'type');
|
|
298
|
+
if (environmentType && environmentType !== 'same-directory') {
|
|
299
|
+
throw new Error('Canon only supports same-directory codex_app.fork_thread calls.');
|
|
300
|
+
}
|
|
301
|
+
const sourceThreadId = readString(args, 'threadId') ?? runtime.currentThreadId;
|
|
302
|
+
if (!sourceThreadId)
|
|
303
|
+
throw new Error('fork_thread requires a source thread id.');
|
|
304
|
+
const source = await assertThreadAllowed(runtime, sourceThreadId);
|
|
305
|
+
const forked = await runtime.adapter.requestAppServer('thread/fork', {
|
|
306
|
+
threadId: sourceThreadId,
|
|
307
|
+
cwd: source.cwd ?? runtime.currentCwd,
|
|
308
|
+
excludeTurns: true,
|
|
309
|
+
persistExtendedHistory: true,
|
|
310
|
+
});
|
|
311
|
+
const threadId = readThreadId(forked);
|
|
312
|
+
if (!threadId)
|
|
313
|
+
throw new Error('Codex app-server did not return a forked thread id.');
|
|
314
|
+
return toolResult(true, {
|
|
315
|
+
threadId,
|
|
316
|
+
sourceThreadId,
|
|
317
|
+
status: 'forked',
|
|
318
|
+
cwd: source.cwd ?? runtime.currentCwd,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
async function listThreads(runtime, args) {
|
|
322
|
+
const limit = clampLimit(readNumber(args, 'limit'));
|
|
323
|
+
const result = await runtime.adapter.requestAppServer('thread/list', {
|
|
324
|
+
cwd: allowedCwds(runtime),
|
|
325
|
+
limit,
|
|
326
|
+
searchTerm: readString(args, 'query') ?? readString(args, 'searchTerm') ?? null,
|
|
327
|
+
archived: typeof args.archived === 'boolean' ? args.archived : false,
|
|
328
|
+
});
|
|
329
|
+
return toolResult(true, result);
|
|
330
|
+
}
|
|
331
|
+
async function readThread(runtime, args) {
|
|
332
|
+
const threadId = readString(args, 'threadId');
|
|
333
|
+
if (!threadId)
|
|
334
|
+
throw new Error('read_thread requires threadId.');
|
|
335
|
+
await assertThreadAllowed(runtime, threadId);
|
|
336
|
+
const result = await runtime.adapter.requestAppServer('thread/read', {
|
|
337
|
+
threadId,
|
|
338
|
+
includeTurns: true,
|
|
339
|
+
});
|
|
340
|
+
return toolResult(true, result);
|
|
341
|
+
}
|
|
342
|
+
async function sendMessageToThread(runtime, args) {
|
|
343
|
+
const threadId = readString(args, 'threadId');
|
|
344
|
+
const prompt = readString(args, 'prompt');
|
|
345
|
+
if (!threadId)
|
|
346
|
+
throw new Error('send_message_to_thread requires threadId.');
|
|
347
|
+
if (!prompt)
|
|
348
|
+
throw new Error('send_message_to_thread requires a non-empty prompt.');
|
|
349
|
+
await assertThreadAllowed(runtime, threadId);
|
|
350
|
+
const result = await runtime.adapter.requestAppServer('turn/start', buildTurnStartParams({
|
|
351
|
+
threadId,
|
|
352
|
+
prompt,
|
|
353
|
+
model: readString(args, 'model') ?? null,
|
|
354
|
+
effort: readString(args, 'thinking') ?? readString(args, 'effort') ?? null,
|
|
355
|
+
}));
|
|
356
|
+
return toolResult(true, {
|
|
357
|
+
threadId,
|
|
358
|
+
turnId: readTurnId(result) ?? null,
|
|
359
|
+
status: 'submitted',
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
async function setThreadArchived(runtime, args) {
|
|
363
|
+
const threadId = readString(args, 'threadId') ?? runtime.currentThreadId;
|
|
364
|
+
if (!threadId)
|
|
365
|
+
throw new Error('set_thread_archived requires threadId outside the calling thread.');
|
|
366
|
+
if (typeof args.archived !== 'boolean')
|
|
367
|
+
throw new Error('set_thread_archived requires archived boolean.');
|
|
368
|
+
await assertThreadAllowed(runtime, threadId);
|
|
369
|
+
const result = await runtime.adapter.requestAppServer(args.archived ? 'thread/archive' : 'thread/unarchive', { threadId });
|
|
370
|
+
return toolResult(true, { threadId, archived: args.archived, result });
|
|
371
|
+
}
|
|
372
|
+
async function setThreadTitle(runtime, args) {
|
|
373
|
+
const threadId = readString(args, 'threadId');
|
|
374
|
+
const title = readString(args, 'title');
|
|
375
|
+
if (!threadId)
|
|
376
|
+
throw new Error('set_thread_title requires threadId.');
|
|
377
|
+
if (!title)
|
|
378
|
+
throw new Error('set_thread_title requires a non-empty title.');
|
|
379
|
+
await assertThreadAllowed(runtime, threadId);
|
|
380
|
+
const result = await runtime.adapter.requestAppServer('thread/name/set', {
|
|
381
|
+
threadId,
|
|
382
|
+
name: title,
|
|
383
|
+
});
|
|
384
|
+
return toolResult(true, { threadId, title, result });
|
|
385
|
+
}
|
|
386
|
+
function resolveCreateThreadCwd(runtime, args) {
|
|
387
|
+
const target = isRecord(args.target) ? args.target : null;
|
|
388
|
+
const environment = isRecord(target?.environment) ? target.environment : null;
|
|
389
|
+
const environmentType = readString(environment, 'type');
|
|
390
|
+
if (environmentType === 'worktree') {
|
|
391
|
+
throw new Error('Canon does not create codex_app threads in new worktrees yet.');
|
|
392
|
+
}
|
|
393
|
+
if (readString(target, 'type') === 'projectless') {
|
|
394
|
+
throw new Error('Canon does not support projectless codex_app threads yet.');
|
|
395
|
+
}
|
|
396
|
+
const projectId = readString(target, 'projectId')
|
|
397
|
+
?? readString(args, 'projectId')
|
|
398
|
+
?? readString(args, 'workspaceId');
|
|
399
|
+
if (!projectId)
|
|
400
|
+
return runtime.currentCwd;
|
|
401
|
+
const workspace = listToolWorkspaces(runtime).find((entry) => entry.id === projectId);
|
|
402
|
+
if (!workspace)
|
|
403
|
+
throw new Error(`Unknown Canon workspace/project id: ${projectId}`);
|
|
404
|
+
return workspace.cwd;
|
|
405
|
+
}
|
|
406
|
+
async function assertThreadAllowed(runtime, threadId) {
|
|
407
|
+
if (threadId === runtime.currentThreadId) {
|
|
408
|
+
return { cwd: runtime.currentCwd, raw: null };
|
|
409
|
+
}
|
|
410
|
+
const result = await runtime.adapter.requestAppServer('thread/read', {
|
|
411
|
+
threadId,
|
|
412
|
+
includeTurns: false,
|
|
413
|
+
});
|
|
414
|
+
const cwd = readThreadCwd(result);
|
|
415
|
+
if (!cwd || !allowedCwdSet(runtime).has(cwd)) {
|
|
416
|
+
throw new Error(`Thread ${threadId} is not in a Canon-configured workspace.`);
|
|
417
|
+
}
|
|
418
|
+
return { cwd, raw: result };
|
|
419
|
+
}
|
|
420
|
+
function buildTurnStartParams(input) {
|
|
421
|
+
return {
|
|
422
|
+
threadId: input.threadId,
|
|
423
|
+
input: [{ type: 'text', text: input.prompt, text_elements: [] }],
|
|
424
|
+
...(input.model ? { model: input.model } : {}),
|
|
425
|
+
...(input.effort ? { effort: input.effort } : {}),
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
function listToolWorkspaces(runtime) {
|
|
429
|
+
const seen = new Set();
|
|
430
|
+
const workspaces = [];
|
|
431
|
+
for (const workspace of runtime.workspaces) {
|
|
432
|
+
if (!workspace.cwd || seen.has(workspace.cwd))
|
|
433
|
+
continue;
|
|
434
|
+
seen.add(workspace.cwd);
|
|
435
|
+
workspaces.push(workspace);
|
|
436
|
+
}
|
|
437
|
+
if (runtime.currentCwd && !seen.has(runtime.currentCwd)) {
|
|
438
|
+
workspaces.unshift({
|
|
439
|
+
id: 'current',
|
|
440
|
+
label: 'Current session',
|
|
441
|
+
cwd: runtime.currentCwd,
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
return workspaces;
|
|
445
|
+
}
|
|
446
|
+
function allowedCwds(runtime) {
|
|
447
|
+
return listToolWorkspaces(runtime).map((workspace) => workspace.cwd);
|
|
448
|
+
}
|
|
449
|
+
function allowedCwdSet(runtime) {
|
|
450
|
+
return new Set(allowedCwds(runtime));
|
|
451
|
+
}
|
|
452
|
+
function clampLimit(value) {
|
|
453
|
+
if (!value || !Number.isFinite(value))
|
|
454
|
+
return 20;
|
|
455
|
+
return Math.max(1, Math.min(100, Math.floor(value)));
|
|
456
|
+
}
|
|
457
|
+
function parseToolArguments(value) {
|
|
458
|
+
if (isRecord(value))
|
|
459
|
+
return value;
|
|
460
|
+
if (typeof value === 'string' && value.trim()) {
|
|
461
|
+
try {
|
|
462
|
+
const parsed = JSON.parse(value);
|
|
463
|
+
return isRecord(parsed) ? parsed : {};
|
|
464
|
+
}
|
|
465
|
+
catch {
|
|
466
|
+
return {};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return {};
|
|
470
|
+
}
|
|
471
|
+
function normalizeToolName(value) {
|
|
472
|
+
if (typeof value !== 'string' || !value.trim())
|
|
473
|
+
return null;
|
|
474
|
+
const trimmed = value.trim();
|
|
475
|
+
return trimmed.startsWith('codex_app.') ? trimmed.slice('codex_app.'.length) : trimmed;
|
|
476
|
+
}
|
|
477
|
+
function readThreadId(value) {
|
|
478
|
+
if (!isRecord(value))
|
|
479
|
+
return undefined;
|
|
480
|
+
return readString(value, 'threadId')
|
|
481
|
+
?? readString(value, 'id')
|
|
482
|
+
?? readString(isRecord(value.thread) ? value.thread : undefined, 'id');
|
|
483
|
+
}
|
|
484
|
+
function readTurnId(value) {
|
|
485
|
+
if (!isRecord(value))
|
|
486
|
+
return undefined;
|
|
487
|
+
return readString(value, 'turnId')
|
|
488
|
+
?? readString(value, 'id')
|
|
489
|
+
?? readString(isRecord(value.turn) ? value.turn : undefined, 'id');
|
|
490
|
+
}
|
|
491
|
+
function readThreadCwd(value) {
|
|
492
|
+
if (!isRecord(value))
|
|
493
|
+
return null;
|
|
494
|
+
const thread = isRecord(value.thread) ? value.thread : undefined;
|
|
495
|
+
return readString(thread, 'cwd')
|
|
496
|
+
?? readString(thread, 'workingDirectory')
|
|
497
|
+
?? readString(value, 'cwd')
|
|
498
|
+
?? null;
|
|
499
|
+
}
|
|
500
|
+
function readString(record, key) {
|
|
501
|
+
const value = record?.[key];
|
|
502
|
+
return typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
|
503
|
+
}
|
|
504
|
+
function readNumber(record, key) {
|
|
505
|
+
const value = record[key];
|
|
506
|
+
return typeof value === 'number' ? value : undefined;
|
|
507
|
+
}
|
|
508
|
+
function isRecord(value) {
|
|
509
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
510
|
+
}
|
|
511
|
+
function toolResult(success, payload) {
|
|
512
|
+
return {
|
|
513
|
+
success,
|
|
514
|
+
contentItems: [{
|
|
515
|
+
type: 'inputText',
|
|
516
|
+
text: typeof payload === 'string' ? payload : JSON.stringify(payload, null, 2),
|
|
517
|
+
}],
|
|
518
|
+
};
|
|
519
|
+
}
|
package/dist/host.js
CHANGED
|
@@ -10,6 +10,7 @@ import { RUNTIME_NEW_SESSION_ACTION, RUNTIME_STOP_ACTION, RUNTIME_STOP_AND_DROP_
|
|
|
10
10
|
import { decideAutoReply, } from './inbound-policy.js';
|
|
11
11
|
import { CodexConversationAdapter, } from './adapter.js';
|
|
12
12
|
import { CodexAppServerAdapter } from './app-server-adapter.js';
|
|
13
|
+
import { CODEX_APP_DYNAMIC_TOOLS, deniedCodexAppToolResult, handleCodexAppToolCall, isCodexAppToolCall, } from './codex-app-tools.js';
|
|
13
14
|
import { mapCanonApprovalResultToCodexDecision, mapCodexAppServerApprovalRequest, } from './app-server-approval.js';
|
|
14
15
|
import { clearStoredThreadId, buildCodexThreadPolicyFingerprint, loadStoredThreadId, saveStoredThreadId, } from './session-store.js';
|
|
15
16
|
import { deriveCodexPermissionEnvelope, mapCanonPermissionToCodex, } from './permission-mode.js';
|
|
@@ -849,6 +850,7 @@ export async function main() {
|
|
|
849
850
|
session.currentTurnId = null;
|
|
850
851
|
session.currentTurnOpenedAt = null;
|
|
851
852
|
session.currentTurnUpdatedAt = null;
|
|
853
|
+
session.currentTurnCanUseCodexAppTools = false;
|
|
852
854
|
session.lastAcceptedIntent = null;
|
|
853
855
|
session.resetRequested = false;
|
|
854
856
|
}
|
|
@@ -923,6 +925,7 @@ export async function main() {
|
|
|
923
925
|
configOverrides: args.config ?? [],
|
|
924
926
|
fullAuto: policy.fullAuto,
|
|
925
927
|
bypassApprovalsAndSandbox: policy.bypassApprovalsAndSandbox,
|
|
928
|
+
dynamicTools: CODEX_APP_DYNAMIC_TOOLS,
|
|
926
929
|
})
|
|
927
930
|
: new CodexConversationAdapter({
|
|
928
931
|
cwd: sessionCwd,
|
|
@@ -955,6 +958,7 @@ export async function main() {
|
|
|
955
958
|
currentTurnId: null,
|
|
956
959
|
currentTurnOpenedAt: null,
|
|
957
960
|
currentTurnUpdatedAt: null,
|
|
961
|
+
currentTurnCanUseCodexAppTools: false,
|
|
958
962
|
activeSelfContextId: null,
|
|
959
963
|
lastAcceptedIntent: null,
|
|
960
964
|
resetRequested: false,
|
|
@@ -985,7 +989,7 @@ export async function main() {
|
|
|
985
989
|
pendingSessionCreations.delete(conversationId);
|
|
986
990
|
}
|
|
987
991
|
}
|
|
988
|
-
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false, artifactRoutingMode = 'disabled') {
|
|
992
|
+
function enqueuePrompt(session, prompt, intent = 'queue', toFront = false, sourceMessageId, markAccepted = false, imagePaths = [], mediaAddDirs = [], planMode = false, artifactRoutingMode = 'disabled', canUseCodexAppTools = false) {
|
|
989
993
|
const nextPrompt = {
|
|
990
994
|
prompt,
|
|
991
995
|
intent,
|
|
@@ -995,6 +999,7 @@ export async function main() {
|
|
|
995
999
|
mediaAddDirs,
|
|
996
1000
|
planMode,
|
|
997
1001
|
artifactRoutingMode,
|
|
1002
|
+
canUseCodexAppTools,
|
|
998
1003
|
};
|
|
999
1004
|
if (toFront) {
|
|
1000
1005
|
session.queue.unshift(nextPrompt);
|
|
@@ -1098,6 +1103,22 @@ export async function main() {
|
|
|
1098
1103
|
const requestId = String(request.id);
|
|
1099
1104
|
const params = request.params;
|
|
1100
1105
|
const expiresAt = Date.now() + 30 * 60_000;
|
|
1106
|
+
if (request.method === 'item/tool/call' && isCodexAppToolCall(params)) {
|
|
1107
|
+
if (!(session.adapter instanceof CodexAppServerAdapter)) {
|
|
1108
|
+
return deniedCodexAppToolResult('codex_app tools require the Codex app-server transport.');
|
|
1109
|
+
}
|
|
1110
|
+
if (!session.currentTurnCanUseCodexAppTools) {
|
|
1111
|
+
return deniedCodexAppToolResult('Only the Canon owner can use codex_app tools.');
|
|
1112
|
+
}
|
|
1113
|
+
return await handleCodexAppToolCall({
|
|
1114
|
+
adapter: session.adapter,
|
|
1115
|
+
currentThreadId: session.adapter.getThreadId(),
|
|
1116
|
+
currentCwd: session.cwd,
|
|
1117
|
+
workspaces: workspaceOptions,
|
|
1118
|
+
model: session.state.model ?? null,
|
|
1119
|
+
effort: session.state.effort ?? null,
|
|
1120
|
+
}, params);
|
|
1121
|
+
}
|
|
1101
1122
|
const runtimeCardPayload = runtimeCardRequestPayload(request.method, params);
|
|
1102
1123
|
if (runtimeCardPayload) {
|
|
1103
1124
|
const card = parseRuntimeCardV1(runtimeCardPayload);
|
|
@@ -1287,7 +1308,7 @@ export async function main() {
|
|
|
1287
1308
|
: decision === 'reject'
|
|
1288
1309
|
? `The plan was declined — keep planning and wait for guidance before implementing.${feedback ? `\n\nNotes:\n${feedback}` : ''}`
|
|
1289
1310
|
: `Please revise the plan.${feedback ? `\n\nRevision feedback:\n${feedback}` : ''}`;
|
|
1290
|
-
enqueuePrompt(session, prompt, 'queue', false, input.message.id, false, [], [], decision !== 'approve');
|
|
1311
|
+
enqueuePrompt(session, prompt, 'queue', false, input.message.id, false, [], [], decision !== 'approve', 'disabled', input.isOwner);
|
|
1291
1312
|
return;
|
|
1292
1313
|
}
|
|
1293
1314
|
let materialized = [];
|
|
@@ -1384,7 +1405,7 @@ export async function main() {
|
|
|
1384
1405
|
});
|
|
1385
1406
|
if (session.running && deliveryIntent === 'interrupt') {
|
|
1386
1407
|
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1387
|
-
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1408
|
+
enqueuePrompt(session, prompt, deliveryIntent, true, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode, participantContext.isOwner);
|
|
1388
1409
|
console.error(`[canon-codex] [${input.conversationId.slice(0, 8)}] Interrupting current turn for explicit human send-now`);
|
|
1389
1410
|
await session.adapter.interrupt().catch(() => { });
|
|
1390
1411
|
clearStreaming(input.conversationId);
|
|
@@ -1392,7 +1413,7 @@ export async function main() {
|
|
|
1392
1413
|
return;
|
|
1393
1414
|
}
|
|
1394
1415
|
const artifactRoutingMode = resolveArtifactRoutingMode(participantContext);
|
|
1395
|
-
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode);
|
|
1416
|
+
enqueuePrompt(session, prompt, deliveryIntent, false, input.message.id, shouldMarkAccepted, imagePaths, mediaAddDirs, planCommand.planMode, artifactRoutingMode, participantContext.isOwner);
|
|
1396
1417
|
}
|
|
1397
1418
|
function sendTurnArtifactFile(session, file) {
|
|
1398
1419
|
return sendMediaFileMessage(client, session.conversationId, file.path, '', {
|
|
@@ -1450,6 +1471,7 @@ export async function main() {
|
|
|
1450
1471
|
session.turnCommandBlocks = createCommandBlockTracker();
|
|
1451
1472
|
session.currentTurnOpenedAt = Date.now();
|
|
1452
1473
|
session.currentTurnUpdatedAt = session.currentTurnOpenedAt;
|
|
1474
|
+
session.currentTurnCanUseCodexAppTools = nextTurn.canUseCodexAppTools === true;
|
|
1453
1475
|
session.lastAcceptedIntent = nextTurn.intent;
|
|
1454
1476
|
session.turnState = 'thinking';
|
|
1455
1477
|
session.lastActivity = Date.now();
|
|
@@ -1715,6 +1737,7 @@ export async function main() {
|
|
|
1715
1737
|
session.currentTurnId = null;
|
|
1716
1738
|
session.currentTurnOpenedAt = null;
|
|
1717
1739
|
session.currentTurnUpdatedAt = null;
|
|
1740
|
+
session.currentTurnCanUseCodexAppTools = false;
|
|
1718
1741
|
session.lastAcceptedIntent = null;
|
|
1719
1742
|
session.resetRequested = false;
|
|
1720
1743
|
session.lastActivity = Date.now();
|
package/dist/session-store.js
CHANGED
|
@@ -17,7 +17,7 @@ function saveStore(store) {
|
|
|
17
17
|
}
|
|
18
18
|
export function buildCodexThreadPolicyFingerprint(input) {
|
|
19
19
|
return createHash('sha256').update(JSON.stringify({
|
|
20
|
-
version:
|
|
20
|
+
version: 2,
|
|
21
21
|
baseCwd: input.baseCwd,
|
|
22
22
|
executionMode: input.executionMode ?? null,
|
|
23
23
|
permissionMode: input.permissionMode ?? null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonmsg/codex-plugin",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.12",
|
|
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.4.
|
|
32
|
+
"@canonmsg/agent-sdk": "^3.4.2",
|
|
33
33
|
"@canonmsg/coding-agent-host": "^0.2.2",
|
|
34
|
-
"@canonmsg/core": "^
|
|
34
|
+
"@canonmsg/core": "^3.0.0"
|
|
35
35
|
},
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=18.0.0"
|