@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk 0.1.19 → 0.1.21

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.
@@ -2,6 +2,9 @@ import {
2
2
  buildRequestedSkillsUserPrompt,
3
3
  SkillsLoader
4
4
  } from "@nextclaw/core";
5
+ import { mkdtempSync, writeFileSync } from "node:fs";
6
+ import { tmpdir } from "node:os";
7
+ import { join } from "node:path";
5
8
  function readString(value) {
6
9
  if (typeof value !== "string") {
7
10
  return void 0;
@@ -17,25 +20,61 @@ function readRequestedSkills(metadata) {
17
20
  return raw.map((entry) => readString(entry)).filter((entry) => Boolean(entry)).slice(0, 8);
18
21
  }
19
22
  function readUserText(input) {
23
+ const message = readLatestUserMessage(input);
24
+ if (!message) {
25
+ return "";
26
+ }
27
+ return message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
28
+ }
29
+ function readLatestUserMessage(input) {
20
30
  for (let index = input.messages.length - 1; index >= 0; index -= 1) {
21
31
  const message = input.messages[index];
22
32
  if (message?.role !== "user") {
23
33
  continue;
24
34
  }
25
- const text = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("").trim();
26
- if (text) {
27
- return text;
28
- }
35
+ return message;
29
36
  }
30
- return "";
37
+ return null;
38
+ }
39
+ function sanitizeFileName(name) {
40
+ const sanitized = name.trim().replace(/[^a-zA-Z0-9._-]+/g, "_");
41
+ return sanitized || "attachment";
42
+ }
43
+ function buildAttachmentPrompt(message) {
44
+ if (!message) {
45
+ return "";
46
+ }
47
+ const fileParts = message.parts.filter((part) => part.type === "file");
48
+ if (fileParts.length === 0) {
49
+ return "";
50
+ }
51
+ const tempDir = mkdtempSync(join(tmpdir(), "nextclaw-codex-files-"));
52
+ const lines = fileParts.map((part, index) => {
53
+ const rawName = readString(part.name) ?? `attachment-${index + 1}`;
54
+ const fileName = sanitizeFileName(rawName);
55
+ const mimeSegment = readString(part.mimeType) ? ` (${part.mimeType})` : "";
56
+ const contentBase64 = readString(part.contentBase64);
57
+ if (contentBase64) {
58
+ const filePath = join(tempDir, fileName);
59
+ writeFileSync(filePath, Buffer.from(contentBase64.replace(/\s+/g, ""), "base64"));
60
+ return `- ${fileName}${mimeSegment} -> local file: ${filePath}`;
61
+ }
62
+ if (readString(part.url)) {
63
+ return `- ${fileName}${mimeSegment} -> remote url: ${part.url}`;
64
+ }
65
+ return `- ${fileName}${mimeSegment}`;
66
+ });
67
+ return ["Attached files for this turn:", ...lines].join("\n");
31
68
  }
32
69
  function buildCodexInputBuilder(workspace) {
33
70
  const skillsLoader = new SkillsLoader(workspace);
34
71
  return async (input) => {
35
72
  const userText = readUserText(input);
73
+ const attachmentPrompt = buildAttachmentPrompt(readLatestUserMessage(input));
74
+ const promptBody = [userText || (attachmentPrompt ? "Please inspect the attached file(s) and respond." : ""), attachmentPrompt].filter(Boolean).join("\n\n");
36
75
  const metadata = input.metadata && typeof input.metadata === "object" && !Array.isArray(input.metadata) ? input.metadata : {};
37
76
  const requestedSkills = readRequestedSkills(metadata);
38
- return buildRequestedSkillsUserPrompt(skillsLoader, requestedSkills, userText);
77
+ return buildRequestedSkillsUserPrompt(skillsLoader, requestedSkills, promptBody);
39
78
  };
40
79
  }
41
80
  export {
package/dist/index.js CHANGED
@@ -12,11 +12,6 @@ import {
12
12
  buildCodexBridgeModelProviderId,
13
13
  resolveExternalModelProvider
14
14
  } from "./codex-model-provider.js";
15
- import {
16
- CODEX_APPROVAL_POLICY,
17
- mapAccessModeToSandboxMode,
18
- resolveCodexAccessMode
19
- } from "./codex-access-mode.js";
20
15
  import { buildCodexInputBuilder } from "./codex-input-builder.js";
21
16
  import { ensureCodexOpenAiResponsesBridge } from "./codex-openai-responses-bridge.js";
22
17
  import { resolveCodexResponsesApiSupport } from "./codex-responses-capability.js";
@@ -228,7 +223,6 @@ const plugin = {
228
223
  config: nextConfig,
229
224
  pluginConfig
230
225
  });
231
- const accessMode = resolveCodexAccessMode(pluginConfig);
232
226
  const thinkingLevel = readThinkingLevel(runtimeParams.sessionMetadata.preferred_thinking) ?? readThinkingLevel(runtimeParams.sessionMetadata.thinking) ?? void 0;
233
227
  return new CodexSdkNcpAgentRuntime({
234
228
  sessionId: runtimeParams.sessionId,
@@ -251,14 +245,14 @@ const plugin = {
251
245
  inputBuilder: buildCodexInputBuilder(executionOptions.workingDirectory),
252
246
  threadOptions: {
253
247
  model,
254
- sandboxMode: mapAccessModeToSandboxMode(accessMode),
248
+ sandboxMode: readString(pluginConfig.sandboxMode),
255
249
  workingDirectory: executionOptions.workingDirectory,
256
250
  skipGitRepoCheck: executionOptions.skipGitRepoCheck,
257
251
  modelReasoningEffort: thinkingLevel,
258
252
  networkAccessEnabled: readBoolean(pluginConfig.networkAccessEnabled),
259
253
  webSearchMode: readString(pluginConfig.webSearchMode),
260
254
  webSearchEnabled: readBoolean(pluginConfig.webSearchEnabled),
261
- approvalPolicy: CODEX_APPROVAL_POLICY,
255
+ approvalPolicy: readString(pluginConfig.approvalPolicy),
262
256
  additionalDirectories: readStringArray(pluginConfig.additionalDirectories)
263
257
  }
264
258
  });
@@ -23,10 +23,9 @@
23
23
  "workingDirectory": {
24
24
  "type": "string"
25
25
  },
26
- "accessMode": {
26
+ "sandboxMode": {
27
27
  "type": "string",
28
- "enum": ["read-only", "workspace-write", "full-access"],
29
- "default": "full-access"
28
+ "enum": ["read-only", "workspace-write", "danger-full-access"]
30
29
  },
31
30
  "skipGitRepoCheck": {
32
31
  "type": "boolean"
@@ -41,6 +40,10 @@
41
40
  "webSearchEnabled": {
42
41
  "type": "boolean"
43
42
  },
43
+ "approvalPolicy": {
44
+ "type": "string",
45
+ "enum": ["never", "on-request", "on-failure", "untrusted"]
46
+ },
44
47
  "codexPathOverride": {
45
48
  "type": "string"
46
49
  },
@@ -83,9 +86,12 @@
83
86
  "label": "Working Directory",
84
87
  "advanced": true
85
88
  },
86
- "accessMode": {
87
- "label": "Access Mode",
88
- "help": "Product-facing permission model. Defaults to full-access; internally this is mapped to the Codex SDK sandbox/approval settings.",
89
+ "sandboxMode": {
90
+ "label": "Sandbox Mode",
91
+ "advanced": true
92
+ },
93
+ "approvalPolicy": {
94
+ "label": "Approval Policy",
89
95
  "advanced": true
90
96
  },
91
97
  "networkAccessEnabled": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/nextclaw-ncp-runtime-plugin-codex-sdk",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "private": false,
5
5
  "description": "NextClaw plugin that registers Codex SDK as an optional NCP runtime.",
6
6
  "type": "module",
@@ -23,8 +23,8 @@
23
23
  "dependencies": {
24
24
  "@nextclaw/core": "0.11.0",
25
25
  "@nextclaw/nextclaw-ncp-runtime-codex-sdk": "0.1.3",
26
- "@nextclaw/ncp-toolkit": "0.4.2",
27
- "@nextclaw/ncp": "0.3.2"
26
+ "@nextclaw/ncp": "0.3.2",
27
+ "@nextclaw/ncp-toolkit": "0.4.2"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^20.17.6",