@co0ontty/wand 4.57.0 → 4.58.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/dist/build-info.json +3 -3
- package/dist/ensure-node-pty-helper.d.ts +0 -1
- package/dist/ensure-node-pty-helper.js +0 -5
- package/dist/git-quick-commit.js +18 -19
- package/dist/github-connector.d.ts +11 -5
- package/dist/github-connector.js +105 -33
- package/dist/missions.d.ts +1 -1
- package/dist/missions.js +17 -4
- package/dist/server-file-routes.js +43 -2
- package/dist/server-github-routes.js +50 -53
- package/dist/server-mission-routes.js +11 -10
- package/dist/server-task-routes.d.ts +14 -2
- package/dist/server-task-routes.js +248 -84
- package/dist/server-workspace-routes.js +90 -16
- package/dist/server.js +2 -20
- package/dist/storage.d.ts +23 -6
- package/dist/storage.js +130 -53
- package/dist/structured-exec-host.d.ts +13 -2
- package/dist/structured-exec-host.js +71 -36
- package/dist/structured-exec-pump.d.ts +1 -1
- package/dist/structured-exec-pump.js +14 -8
- package/dist/task-types.d.ts +15 -0
- package/dist/terminal-daemon-client.d.ts +2 -0
- package/dist/terminal-daemon-client.js +7 -1
- package/dist/terminal-daemon-server.js +17 -18
- package/dist/tui/ipc-server.js +3 -1
- package/dist/types.d.ts +1 -0
- package/dist/wand-task-sync.d.ts +9 -0
- package/dist/wand-task-sync.js +50 -0
- package/dist/web-ui/content/scripts.js +85 -85
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/ws-broadcast.js +5 -1
- package/package.json +2 -3
- package/dist/taskboard-bridge.d.ts +0 -11
- package/dist/taskboard-bridge.js +0 -371
- package/vendor/codex-taskboard/LICENSE +0 -201
- package/vendor/codex-taskboard/dist/web/assets/AiChat-BNn4VJ5a.js +0 -12
- package/vendor/codex-taskboard/dist/web/assets/GanttView-D8wnfGrG.css +0 -1
- package/vendor/codex-taskboard/dist/web/assets/GanttView-rGWhuEX9.js +0 -39
- package/vendor/codex-taskboard/dist/web/assets/abnfDiagram-N423BO3Z-DbFG6lbA.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/arc-BjSQqbzd.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/architecture-TIHT7OUA-C9-t8p4o.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/architectureDiagram-T3A2C74G-Db4kJZwO.js +0 -36
- package/vendor/codex-taskboard/dist/web/assets/array-BifhSqXX.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/blockDiagram-VBNYF7ZC-C-BywuWV.js +0 -132
- package/vendor/codex-taskboard/dist/web/assets/c4Diagram-5PPSVZJV-Dq-80rjP.js +0 -10
- package/vendor/codex-taskboard/dist/web/assets/channel-C7mnz8Xc.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-2GRJ4B5K-B2G57us9.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-3NCLNEKW-DVGIRib_.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-4I5QYGJK-kNoGFqr0.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-5RXB4S5H-4iv6-xM6.js +0 -231
- package/vendor/codex-taskboard/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +0 -15
- package/vendor/codex-taskboard/dist/web/assets/chunk-6Q2QTUOP-1J34vxHr.js +0 -88
- package/vendor/codex-taskboard/dist/web/assets/chunk-7BUUIJ7U-Bb538aSH.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-7Z6QIM7H-CDQpQlxX.js +0 -10
- package/vendor/codex-taskboard/dist/web/assets/chunk-GF5L2VYU-Dg92oHHl.js +0 -206
- package/vendor/codex-taskboard/dist/web/assets/chunk-I66GZJ75-Bk8_gFs2.js +0 -125
- package/vendor/codex-taskboard/dist/web/assets/chunk-J7OUQ5F2-CJRcenlr.js +0 -2
- package/vendor/codex-taskboard/dist/web/assets/chunk-JQJVKLGR-BLBV_Sus.js +0 -156
- package/vendor/codex-taskboard/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-KBJHAD2P-Da9Yjz0p.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-KEIR6QF5-Dj-OpFgW.js +0 -161
- package/vendor/codex-taskboard/dist/web/assets/chunk-NSK5VX7P-Cb56tnGK.js +0 -2
- package/vendor/codex-taskboard/dist/web/assets/chunk-QR6OTTB3-BwoszTxn.js +0 -62
- package/vendor/codex-taskboard/dist/web/assets/chunk-RYQCIY6F-D_L2RdcQ.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-UBXNYLIW-YX5mN8cl.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-W5SLKNZC-DlOLhhDO.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-WRU74C26-B7SBmP0K.js +0 -70
- package/vendor/codex-taskboard/dist/web/assets/chunk-XXDRQBXY-BuE3VzE_.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/chunk-ZIRB5QZD-C6fEPe3t.js +0 -32
- package/vendor/codex-taskboard/dist/web/assets/classDiagram-JCYQIIEL-KIdCVvbA.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/classDiagram-v2-OCEON4UE-KIdCVvbA.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/cose-bilkent-JH36ORCC-BzQvy5TC.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/cynefin-VYW2F7L2-D1_5O1XR.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/cynefinDiagram-MW4NZA55-DOW_U5QN.js +0 -62
- package/vendor/codex-taskboard/dist/web/assets/cytoscape.esm-DnGd5Fuo.js +0 -321
- package/vendor/codex-taskboard/dist/web/assets/dagre-VZM6K2ZE-0Dntc3NT.js +0 -4
- package/vendor/codex-taskboard/dist/web/assets/dagre-dpRSp0QF.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/defaultLocale-C8Fc0cco.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/diagram-7IWD3JNH-D7GleYAM.js +0 -30
- package/vendor/codex-taskboard/dist/web/assets/diagram-B4RE2ZJO-DCXTe6q5.js +0 -3
- package/vendor/codex-taskboard/dist/web/assets/diagram-LBJQPF4R-ZFJyXEXl.js +0 -24
- package/vendor/codex-taskboard/dist/web/assets/diagram-Q27KOJAE-DUGCuGd9.js +0 -24
- package/vendor/codex-taskboard/dist/web/assets/diagram-UB23O5K3-DRmp4Nng.js +0 -41
- package/vendor/codex-taskboard/dist/web/assets/dist-D9sYb5Oa.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/ebnfDiagram-BXEA7PRR-CkKoaf7T.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/erDiagram-JOGREHBK-nBX5z4R4.js +0 -85
- package/vendor/codex-taskboard/dist/web/assets/eventmodeling-45OFAUF4-pexmK65N.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/flowDiagram-UKHOOZJN-aroohRbL.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/ganttDiagram-PKOTCBZU-BOJWUTXa.js +0 -292
- package/vendor/codex-taskboard/dist/web/assets/gitGraph-TEB2WS4Q-DxnuhSZU.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/gitGraphDiagram-DS77QQ5N-Cte975TC.js +0 -106
- package/vendor/codex-taskboard/dist/web/assets/graphlib-DS17s2tU.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/index-Docmx4uQ.css +0 -1
- package/vendor/codex-taskboard/dist/web/assets/index-Du07lEGz.js +0 -121
- package/vendor/codex-taskboard/dist/web/assets/info-DKCQHKI2-DOTjgYxk.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/infoDiagram-6WML65LV-SpyV9UJd.js +0 -2
- package/vendor/codex-taskboard/dist/web/assets/init-D6jRqBbL.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/ishikawaDiagram-WSZJBQD7-T0PZwoUZ.js +0 -70
- package/vendor/codex-taskboard/dist/web/assets/journeyDiagram-NVQOT4AX-B88b8UgX.js +0 -139
- package/vendor/codex-taskboard/dist/web/assets/kanban-definition-27J2QSJJ-CIlfNnl_.js +0 -89
- package/vendor/codex-taskboard/dist/web/assets/katex-B7rAX3Vi.js +0 -257
- package/vendor/codex-taskboard/dist/web/assets/line-fWrwPZhB.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/linear-B7l8qgEw.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/map-BaFkSB1l.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/mermaid-parser.core-C5gls2nC.js +0 -7
- package/vendor/codex-taskboard/dist/web/assets/mermaid.core-POz2Xcvr.js +0 -11
- package/vendor/codex-taskboard/dist/web/assets/mindmap-definition-FAOFIHXS-BM0-egZ5.js +0 -96
- package/vendor/codex-taskboard/dist/web/assets/ordinal-hYBb2elL.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/packet-7NZHBO7P-DQZzyVos.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/path-BWPyau1x.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/pegDiagram-VL7TDLO6-B8aEwl-7.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/pie-RZYD4A2V-DRUflilR.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/pieDiagram-7S7Q4E2Y-0cDDxlua.js +0 -39
- package/vendor/codex-taskboard/dist/web/assets/purify.es-Bwt4ulIK.js +0 -3
- package/vendor/codex-taskboard/dist/web/assets/quadrantDiagram-CIZ2JOQS-CBgb0YZ0.js +0 -7
- package/vendor/codex-taskboard/dist/web/assets/radar-I7S5WNFK-tRocoKxq.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/railroad-3IZDKUUU-DaS4ZlX_.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/railroad-abnf-AHOZXSZD-BN1T5dBT.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/railroad-ebnf-EBAXGLYW-Cuf8qyKs.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/railroad-peg-LSFZ7HO6-B511_EYq.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/railroadDiagram-AXF67PYL-QtPo-JZb.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/requirementDiagram-LRYGKXZP-CuBIF85r.js +0 -84
- package/vendor/codex-taskboard/dist/web/assets/rolldown-runtime-QTnfLwEv.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/rough.esm-CSKSodPl.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/sankeyDiagram-W5VNT64P-CRA-b_za.js +0 -40
- package/vendor/codex-taskboard/dist/web/assets/sequenceDiagram-SI44F4Z6-CoddVWwY.js +0 -162
- package/vendor/codex-taskboard/dist/web/assets/sizeCapture-X5ZJPWSS-B0uUizjq.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/src-_wZywoZs.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/stateDiagram-OKZ733FA-D8DMM2ln.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/stateDiagram-v2-UEYNNEHI-CVp0eYAS.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/swimlanes-SLNWSIFB-C1fC7EFu.js +0 -2
- package/vendor/codex-taskboard/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CPd58Xso.js +0 -8
- package/vendor/codex-taskboard/dist/web/assets/timeline-definition-Z64GVDOM-BUBv8aKP.js +0 -120
- package/vendor/codex-taskboard/dist/web/assets/treeView-QDETBFTQ-C-Bve2kY.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/treemap-6X3UGDF4-BcphHDdV.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/vennDiagram-T6HMQDX7-0wQnwAyA.js +0 -34
- package/vendor/codex-taskboard/dist/web/assets/wardley-OPB4EBWU-CJZ4BevB.js +0 -1
- package/vendor/codex-taskboard/dist/web/assets/wardleyDiagram-T6FBY63Y-U9nwPrkN.js +0 -78
- package/vendor/codex-taskboard/dist/web/assets/xychartDiagram-ELKLHX3M-CyCqnzgE.js +0 -7
- package/vendor/codex-taskboard/dist/web/codex-agent-logo.png +0 -0
- package/vendor/codex-taskboard/dist/web/codex-app-icon.png +0 -0
- package/vendor/codex-taskboard/dist/web/favicon.svg +0 -7
- package/vendor/codex-taskboard/dist/web/index.html +0 -20
- package/vendor/codex-taskboard/server/ai-chat-catalog.mjs +0 -1031
- package/vendor/codex-taskboard/server/ai-chat-process.mjs +0 -462
- package/vendor/codex-taskboard/server/ai-chat.mjs +0 -1237
- package/vendor/codex-taskboard/server/ai-turn-owner.mjs +0 -39
- package/vendor/codex-taskboard/server/app.mjs +0 -3211
- package/vendor/codex-taskboard/server/cloud-config.mjs +0 -188
- package/vendor/codex-taskboard/server/cloud-proxy.mjs +0 -310
- package/vendor/codex-taskboard/server/codex-app-server.mjs +0 -376
- package/vendor/codex-taskboard/server/codex-slash-commands-0.139.0.json +0 -62
- package/vendor/codex-taskboard/server/database.mjs +0 -2845
- package/vendor/codex-taskboard/server/index.mjs +0 -41
- package/vendor/codex-taskboard/server/jira-config.mjs +0 -181
- package/vendor/codex-taskboard/server/jira-integration.mjs +0 -487
- package/vendor/codex-taskboard/server/project-summary.mjs +0 -159
- package/vendor/codex-taskboard/shared/api-fields.mjs +0 -163
- package/vendor/codex-taskboard/shared/codex-environment.mjs +0 -5
- package/vendor/codex-taskboard/shared/codex-executable.mjs +0 -77
- package/vendor/codex-taskboard/shared/codex-permissions.mjs +0 -9
- package/vendor/codex-taskboard/shared/composer-reference.d.mts +0 -19
- package/vendor/codex-taskboard/shared/composer-reference.mjs +0 -90
- package/vendor/codex-taskboard/shared/domain.mjs +0 -34
- package/vendor/codex-taskboard/shared/executable-command.mjs +0 -13
- package/vendor/codex-taskboard/shared/process-tree.mjs +0 -27
- package/vendor/codex-taskboard/shared/task-input.mjs +0 -308
- package/vendor/codex-taskboard/shared/task-records.mjs +0 -199
- package/vendor/codex-taskboard/shared/task-relations.mjs +0 -55
- package/vendor/codex-taskboard/shared/taskboard-automation.mjs +0 -277
- package/vendor/codex-taskboard/skills/manage-taskboard/SKILL.md +0 -45
- package/vendor/codex-taskboard/skills/manage-taskboard/agents/openai.yaml +0 -4
- package/vendor/codex-taskboard/skills/manage-taskboard/references/cli.md +0 -211
|
@@ -1,1031 +0,0 @@
|
|
|
1
|
-
import { execFile, spawn } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
import { parse as parseToml } from "smol-toml";
|
|
8
|
-
|
|
9
|
-
import { withoutTaskboardLauncherEnvironment } from "../shared/codex-environment.mjs";
|
|
10
|
-
import { executableCommand } from "../shared/executable-command.mjs";
|
|
11
|
-
import { composerReferencePersistence } from "../shared/composer-reference.mjs";
|
|
12
|
-
import { ApiError } from "../shared/api-fields.mjs";
|
|
13
|
-
|
|
14
|
-
const execFileAsync = promisify(execFile);
|
|
15
|
-
const CATALOG_TIMEOUT_MS = 10_000;
|
|
16
|
-
const CATALOG_MAX_BUFFER = 2 * 1024 * 1024;
|
|
17
|
-
const COMPOSER_CONTRACT_VERSION = "composer.v1";
|
|
18
|
-
const SLASH_COMMAND_CATALOG_URL = new URL("./codex-slash-commands-0.139.0.json", import.meta.url);
|
|
19
|
-
let slashCommandCatalogPromise;
|
|
20
|
-
|
|
21
|
-
const VERIFIED_SLASH_ACTIONS = [
|
|
22
|
-
{
|
|
23
|
-
command: "/new",
|
|
24
|
-
label: "New conversation",
|
|
25
|
-
description: "Start a new conversation",
|
|
26
|
-
handlerId: "new-conversation",
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
command: "/model",
|
|
30
|
-
label: "Model",
|
|
31
|
-
description: "Choose the model",
|
|
32
|
-
handlerId: "open-model-menu",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
command: "/reasoning",
|
|
36
|
-
label: "Reasoning",
|
|
37
|
-
description: "Choose the reasoning effort",
|
|
38
|
-
handlerId: "open-reasoning-menu",
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
command: "/compact",
|
|
42
|
-
label: "Compact",
|
|
43
|
-
description: "Compact this conversation's context",
|
|
44
|
-
handlerId: "compact-conversation",
|
|
45
|
-
},
|
|
46
|
-
];
|
|
47
|
-
|
|
48
|
-
const UNSUPPORTED_COMPOSER_SOURCES = [
|
|
49
|
-
{ kind: "apps", state: "unsupported", reasonCode: "INVOCATION_NAME_UNAVAILABLE" },
|
|
50
|
-
{ kind: "files", state: "unsupported", reasonCode: "ENCODER_UNSUPPORTED" },
|
|
51
|
-
{ kind: "plugins", state: "unsupported", reasonCode: "EXPERIMENTAL_SOURCE_NOT_ALLOWED" },
|
|
52
|
-
{ kind: "customPrompts", state: "unsupported", reasonCode: "NO_STABLE_CATALOG" },
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
function nonEmptyTomlString(value) {
|
|
56
|
-
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async function readConfiguredAgent(filePath, roleNameHint = null) {
|
|
60
|
-
try {
|
|
61
|
-
const source = await readFile(filePath, "utf8");
|
|
62
|
-
const parsed = parseToml(source);
|
|
63
|
-
const stableId = nonEmptyTomlString(parsed.name)
|
|
64
|
-
?? nonEmptyTomlString(parsed.role_name)
|
|
65
|
-
?? roleNameHint;
|
|
66
|
-
const description = nonEmptyTomlString(parsed.description);
|
|
67
|
-
const developerInstructions = nonEmptyTomlString(parsed.developer_instructions);
|
|
68
|
-
if (!stableId || (roleNameHint === null && (!description || !developerInstructions))) return null;
|
|
69
|
-
return {
|
|
70
|
-
stableId,
|
|
71
|
-
name: stableId,
|
|
72
|
-
label: stableId,
|
|
73
|
-
description,
|
|
74
|
-
developerInstructions,
|
|
75
|
-
sourcePath: filePath,
|
|
76
|
-
};
|
|
77
|
-
} catch {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
async function collectTomlFiles(directory) {
|
|
83
|
-
const files = [];
|
|
84
|
-
const pendingDirectories = [directory];
|
|
85
|
-
let available = false;
|
|
86
|
-
while (pendingDirectories.length > 0) {
|
|
87
|
-
const currentDirectory = pendingDirectories.pop();
|
|
88
|
-
let entries;
|
|
89
|
-
try {
|
|
90
|
-
entries = await readdir(currentDirectory, { withFileTypes: true });
|
|
91
|
-
available = true;
|
|
92
|
-
} catch (error) {
|
|
93
|
-
if (error?.code === "ENOENT") continue;
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
97
|
-
const entryPath = path.join(currentDirectory, entry.name);
|
|
98
|
-
if (entry.isDirectory()) {
|
|
99
|
-
pendingDirectories.push(entryPath);
|
|
100
|
-
} else if (entry.isFile() && entry.name.endsWith(".toml")) {
|
|
101
|
-
files.push(entryPath);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return { files: files.sort(), available };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async function projectConfigFolders(workspacePath) {
|
|
109
|
-
if (typeof workspacePath !== "string" || !workspacePath.trim()) return [];
|
|
110
|
-
const folders = [];
|
|
111
|
-
let current = path.resolve(workspacePath);
|
|
112
|
-
while (true) {
|
|
113
|
-
folders.push(path.join(current, ".codex"));
|
|
114
|
-
try {
|
|
115
|
-
await stat(path.join(current, ".git"));
|
|
116
|
-
break;
|
|
117
|
-
} catch {}
|
|
118
|
-
const parent = path.dirname(current);
|
|
119
|
-
if (parent === current) break;
|
|
120
|
-
current = parent;
|
|
121
|
-
}
|
|
122
|
-
return folders.reverse();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function mergeConfiguredAgent(high, low) {
|
|
126
|
-
return {
|
|
127
|
-
...low,
|
|
128
|
-
...high,
|
|
129
|
-
description: high.description ?? low.description,
|
|
130
|
-
developerInstructions: high.developerInstructions ?? low.developerInstructions,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function completeConfiguredAgent(agent) {
|
|
135
|
-
return agent
|
|
136
|
-
&& agent.stableId
|
|
137
|
-
&& agent.description
|
|
138
|
-
? {
|
|
139
|
-
...agent,
|
|
140
|
-
identity: ["agent", agent.stableId].join("\u0000"),
|
|
141
|
-
id: agent.stableId,
|
|
142
|
-
}
|
|
143
|
-
: null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
async function loadAgentLayer({ configDirectory, configFile, agentsDirectory }) {
|
|
147
|
-
const layerAgents = new Map();
|
|
148
|
-
const declaredFiles = new Set();
|
|
149
|
-
let available = false;
|
|
150
|
-
let config = null;
|
|
151
|
-
try {
|
|
152
|
-
config = parseToml(await readFile(configFile, "utf8"));
|
|
153
|
-
available = true;
|
|
154
|
-
} catch {}
|
|
155
|
-
for (const [declaredName, role] of Object.entries(config?.agents ?? {})) {
|
|
156
|
-
if (!role || typeof role !== "object" || Array.isArray(role)) continue;
|
|
157
|
-
const configFileValue = nonEmptyTomlString(role.config_file);
|
|
158
|
-
let agent = {
|
|
159
|
-
stableId: declaredName,
|
|
160
|
-
name: declaredName,
|
|
161
|
-
label: declaredName,
|
|
162
|
-
description: nonEmptyTomlString(role.description),
|
|
163
|
-
developerInstructions: nonEmptyTomlString(role.developer_instructions),
|
|
164
|
-
sourcePath: configFile,
|
|
165
|
-
};
|
|
166
|
-
if (configFileValue) {
|
|
167
|
-
const declaredFile = path.isAbsolute(configFileValue)
|
|
168
|
-
? configFileValue
|
|
169
|
-
: path.resolve(configDirectory, configFileValue);
|
|
170
|
-
const fileAgent = await readConfiguredAgent(declaredFile, declaredName);
|
|
171
|
-
if (!fileAgent) continue;
|
|
172
|
-
declaredFiles.add(path.resolve(declaredFile));
|
|
173
|
-
agent = mergeConfiguredAgent(fileAgent, agent);
|
|
174
|
-
}
|
|
175
|
-
if (!layerAgents.has(agent.stableId)) layerAgents.set(agent.stableId, agent);
|
|
176
|
-
}
|
|
177
|
-
const discovered = await collectTomlFiles(agentsDirectory);
|
|
178
|
-
available ||= discovered.available;
|
|
179
|
-
for (const filePath of discovered.files) {
|
|
180
|
-
if (declaredFiles.has(path.resolve(filePath))) continue;
|
|
181
|
-
const agent = await readConfiguredAgent(filePath);
|
|
182
|
-
if (agent && !layerAgents.has(agent.stableId)) layerAgents.set(agent.stableId, agent);
|
|
183
|
-
}
|
|
184
|
-
return { agents: [...layerAgents.values()], available };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
async function listConfiguredAgents({ codexHome, agentsDirectory, workspacePath }) {
|
|
188
|
-
const projectFolders = await projectConfigFolders(workspacePath);
|
|
189
|
-
const layers = [{
|
|
190
|
-
configDirectory: codexHome,
|
|
191
|
-
configFile: path.join(codexHome, "config.toml"),
|
|
192
|
-
agentsDirectory,
|
|
193
|
-
}, ...projectFolders.map((configDirectory) => ({
|
|
194
|
-
configDirectory,
|
|
195
|
-
configFile: path.join(configDirectory, "config.toml"),
|
|
196
|
-
agentsDirectory: path.join(configDirectory, "agents"),
|
|
197
|
-
}))];
|
|
198
|
-
const effective = new Map();
|
|
199
|
-
let available = false;
|
|
200
|
-
for (const layer of layers) {
|
|
201
|
-
const loaded = await loadAgentLayer(layer);
|
|
202
|
-
available ||= loaded.available;
|
|
203
|
-
for (const agent of loaded.agents) {
|
|
204
|
-
const previous = effective.get(agent.stableId);
|
|
205
|
-
const merged = completeConfiguredAgent(
|
|
206
|
-
previous ? mergeConfiguredAgent(agent, previous) : agent,
|
|
207
|
-
);
|
|
208
|
-
if (merged) effective.set(agent.stableId, merged);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
agents: [...effective.values()].sort((left, right) => left.label.localeCompare(right.label)),
|
|
213
|
-
available,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
async function existingDirectory(value) {
|
|
217
|
-
if (typeof value !== "string" || !path.isAbsolute(value.trim())) return null;
|
|
218
|
-
try {
|
|
219
|
-
const resolved = await realpath(value.trim());
|
|
220
|
-
return (await stat(resolved)).isDirectory() ? resolved : null;
|
|
221
|
-
} catch {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export async function loadDeviceWorkspaces(codexStatePath, database) {
|
|
227
|
-
const workspaces = new Map();
|
|
228
|
-
let localProjects = {};
|
|
229
|
-
try {
|
|
230
|
-
const state = JSON.parse(await readFile(codexStatePath, "utf8"));
|
|
231
|
-
if (
|
|
232
|
-
state?.["local-projects"]
|
|
233
|
-
&& typeof state["local-projects"] === "object"
|
|
234
|
-
&& !Array.isArray(state["local-projects"])
|
|
235
|
-
) {
|
|
236
|
-
localProjects = state["local-projects"];
|
|
237
|
-
}
|
|
238
|
-
} catch {}
|
|
239
|
-
|
|
240
|
-
for (const [projectId, project] of Object.entries(localProjects)) {
|
|
241
|
-
if (!Array.isArray(project?.rootPaths)) continue;
|
|
242
|
-
for (const rootPath of project.rootPaths) {
|
|
243
|
-
const workspacePath = await existingDirectory(rootPath);
|
|
244
|
-
if (!workspacePath) continue;
|
|
245
|
-
workspaces.set(projectId, workspacePath);
|
|
246
|
-
break;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
for (const project of await database.listProjects()) {
|
|
251
|
-
if (workspaces.has(project.id)) continue;
|
|
252
|
-
const workspacePath = await existingDirectory(project.workspacePath);
|
|
253
|
-
if (workspacePath) workspaces.set(project.id, workspacePath);
|
|
254
|
-
}
|
|
255
|
-
return workspaces;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function loadMappedWorkspaces(projectMappings) {
|
|
259
|
-
const workspaces = new Map();
|
|
260
|
-
for (const [projectId, mappedPath] of Object.entries(projectMappings)) {
|
|
261
|
-
const workspacePath = await existingDirectory(mappedPath);
|
|
262
|
-
if (workspacePath) workspaces.set(projectId, workspacePath);
|
|
263
|
-
}
|
|
264
|
-
return workspaces;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function resolvedWorkspace(projectId, project, workspaces) {
|
|
268
|
-
if (!project || project.id !== projectId) {
|
|
269
|
-
throw new ApiError(404, "PROJECT_NOT_FOUND", `Project '${projectId}' does not exist`);
|
|
270
|
-
}
|
|
271
|
-
const workspacePath = workspaces.get(projectId);
|
|
272
|
-
if (!workspacePath) {
|
|
273
|
-
throw new ApiError(
|
|
274
|
-
409,
|
|
275
|
-
"PROJECT_WORKSPACE_UNAVAILABLE",
|
|
276
|
-
`Project '${projectId}' has no available device workspace`,
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
workspacePath,
|
|
281
|
-
addDirectories: [...new Set(workspaces.values())].filter((candidate) => candidate !== workspacePath),
|
|
282
|
-
project,
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export async function resolveAiWorkspace(projectId, codexStatePath, database) {
|
|
287
|
-
const project = await database.getProject(projectId);
|
|
288
|
-
const workspaces = await loadDeviceWorkspaces(codexStatePath, database);
|
|
289
|
-
return resolvedWorkspace(projectId, project, workspaces);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
export async function resolveMappedAiWorkspace(projectId, project, projectMappings = {}) {
|
|
293
|
-
const workspaces = await loadMappedWorkspaces(projectMappings);
|
|
294
|
-
return resolvedWorkspace(projectId, project, workspaces);
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function sanitizeModels(value) {
|
|
298
|
-
if (!Array.isArray(value)) throw new Error("Codex returned an invalid model catalog");
|
|
299
|
-
return value.flatMap((model) => {
|
|
300
|
-
if (
|
|
301
|
-
!model
|
|
302
|
-
|| typeof model !== "object"
|
|
303
|
-
|| (model.visibility !== undefined && model.visibility !== "list")
|
|
304
|
-
|| typeof model.slug !== "string"
|
|
305
|
-
|| !model.slug.trim()
|
|
306
|
-
) {
|
|
307
|
-
return [];
|
|
308
|
-
}
|
|
309
|
-
const slug = model.slug.trim();
|
|
310
|
-
const efforts = Array.isArray(model.supported_reasoning_levels)
|
|
311
|
-
? [...new Set(model.supported_reasoning_levels.flatMap((level) => (
|
|
312
|
-
typeof level?.effort === "string" && level.effort.trim() ? [level.effort.trim()] : []
|
|
313
|
-
)))]
|
|
314
|
-
: [];
|
|
315
|
-
const serviceTiers = Array.isArray(model.service_tiers)
|
|
316
|
-
? model.service_tiers.flatMap((tier) => (
|
|
317
|
-
typeof tier?.id === "string"
|
|
318
|
-
&& tier.id.trim()
|
|
319
|
-
&& typeof tier.name === "string"
|
|
320
|
-
&& tier.name.trim()
|
|
321
|
-
? [{ id: tier.id.trim(), name: tier.name.trim() }]
|
|
322
|
-
: []
|
|
323
|
-
))
|
|
324
|
-
: [];
|
|
325
|
-
return [{
|
|
326
|
-
slug,
|
|
327
|
-
displayName: typeof model.display_name === "string" && model.display_name.trim()
|
|
328
|
-
? model.display_name.trim()
|
|
329
|
-
: slug,
|
|
330
|
-
description: typeof model.description === "string" ? model.description : "",
|
|
331
|
-
defaultReasoningEffort: typeof model.default_reasoning_level === "string"
|
|
332
|
-
? model.default_reasoning_level.trim()
|
|
333
|
-
: "",
|
|
334
|
-
supportedReasoningEfforts: efforts,
|
|
335
|
-
serviceTiers,
|
|
336
|
-
}];
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
function sanitizeAppServerModels(value) {
|
|
341
|
-
if (!Array.isArray(value)) throw new Error("Codex returned an invalid model catalog");
|
|
342
|
-
return value.flatMap((model) => {
|
|
343
|
-
if (
|
|
344
|
-
!model
|
|
345
|
-
|| typeof model !== "object"
|
|
346
|
-
|| model.hidden === true
|
|
347
|
-
|| typeof model.model !== "string"
|
|
348
|
-
|| !model.model.trim()
|
|
349
|
-
) return [];
|
|
350
|
-
const slug = model.model.trim();
|
|
351
|
-
const efforts = Array.isArray(model.supportedReasoningEfforts)
|
|
352
|
-
? [...new Set(model.supportedReasoningEfforts.flatMap((entry) => (
|
|
353
|
-
typeof entry?.reasoningEffort === "string" && entry.reasoningEffort.trim()
|
|
354
|
-
? [entry.reasoningEffort.trim()]
|
|
355
|
-
: []
|
|
356
|
-
)))]
|
|
357
|
-
: [];
|
|
358
|
-
const serviceTiers = Array.isArray(model.serviceTiers)
|
|
359
|
-
? model.serviceTiers.flatMap((tier) => (
|
|
360
|
-
typeof tier?.id === "string"
|
|
361
|
-
&& tier.id.trim()
|
|
362
|
-
&& typeof tier.name === "string"
|
|
363
|
-
&& tier.name.trim()
|
|
364
|
-
? [{ id: tier.id.trim(), name: tier.name.trim() }]
|
|
365
|
-
: []
|
|
366
|
-
))
|
|
367
|
-
: [];
|
|
368
|
-
return [{
|
|
369
|
-
slug,
|
|
370
|
-
displayName: typeof model.displayName === "string" && model.displayName.trim()
|
|
371
|
-
? model.displayName.trim()
|
|
372
|
-
: slug,
|
|
373
|
-
description: typeof model.description === "string" ? model.description : "",
|
|
374
|
-
defaultReasoningEffort: typeof model.defaultReasoningEffort === "string"
|
|
375
|
-
? model.defaultReasoningEffort.trim()
|
|
376
|
-
: "",
|
|
377
|
-
supportedReasoningEfforts: efforts,
|
|
378
|
-
serviceTiers,
|
|
379
|
-
}];
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function listSkills(codexExecutable, workspacePath, processEnv) {
|
|
384
|
-
return new Promise((resolve, reject) => {
|
|
385
|
-
const command = executableCommand(codexExecutable, ["app-server", "--stdio"]);
|
|
386
|
-
const child = spawn(command.executable, command.args, {
|
|
387
|
-
cwd: workspacePath,
|
|
388
|
-
env: processEnv,
|
|
389
|
-
stdio: ["pipe", "pipe", "ignore"],
|
|
390
|
-
windowsHide: true,
|
|
391
|
-
});
|
|
392
|
-
let buffer = "";
|
|
393
|
-
let settled = false;
|
|
394
|
-
const timeout = setTimeout(
|
|
395
|
-
() => finish(new Error("Timed out while reading Codex skills")),
|
|
396
|
-
CATALOG_TIMEOUT_MS,
|
|
397
|
-
);
|
|
398
|
-
|
|
399
|
-
function finish(error, value) {
|
|
400
|
-
if (settled) return;
|
|
401
|
-
settled = true;
|
|
402
|
-
clearTimeout(timeout);
|
|
403
|
-
child.stdin.end();
|
|
404
|
-
child.kill("SIGTERM");
|
|
405
|
-
if (error) reject(error);
|
|
406
|
-
else resolve(value);
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function send(message) {
|
|
410
|
-
child.stdin.write(`${JSON.stringify(message)}\n`);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
function handleMessage(message) {
|
|
414
|
-
if (message?.id === 1) {
|
|
415
|
-
if (message.error) return finish(new Error("Codex app-server rejected initialization"));
|
|
416
|
-
send({ method: "initialized" });
|
|
417
|
-
send({
|
|
418
|
-
id: 2,
|
|
419
|
-
method: "skills/list",
|
|
420
|
-
params: { cwds: [workspacePath], forceReload: false },
|
|
421
|
-
});
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
if (message?.id !== 2) return;
|
|
425
|
-
if (message.error) return finish(new Error("Codex app-server could not list skills"));
|
|
426
|
-
finish(null, Array.isArray(message.result?.data) ? message.result.data : []);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
child.stdout.setEncoding("utf8");
|
|
430
|
-
child.stdout.on("data", (chunk) => {
|
|
431
|
-
buffer += chunk;
|
|
432
|
-
if (buffer.length > CATALOG_MAX_BUFFER) {
|
|
433
|
-
finish(new Error("Codex skills response exceeded the catalog size limit"));
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
let newlineIndex = buffer.indexOf("\n");
|
|
437
|
-
while (newlineIndex >= 0 && !settled) {
|
|
438
|
-
const line = buffer.slice(0, newlineIndex).trim();
|
|
439
|
-
buffer = buffer.slice(newlineIndex + 1);
|
|
440
|
-
if (line) {
|
|
441
|
-
try {
|
|
442
|
-
handleMessage(JSON.parse(line));
|
|
443
|
-
} catch {}
|
|
444
|
-
}
|
|
445
|
-
newlineIndex = buffer.indexOf("\n");
|
|
446
|
-
}
|
|
447
|
-
});
|
|
448
|
-
child.stdin.on("error", (error) => finish(error));
|
|
449
|
-
child.once("error", (error) => finish(error));
|
|
450
|
-
child.once("exit", (code, signal) => {
|
|
451
|
-
if (!settled) {
|
|
452
|
-
finish(new Error(`Codex app-server exited before listing skills (${signal || code})`));
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
child.once("spawn", () => {
|
|
456
|
-
send({
|
|
457
|
-
id: 1,
|
|
458
|
-
method: "initialize",
|
|
459
|
-
params: {
|
|
460
|
-
clientInfo: { name: "codex-taskboard", version: "0.1.0" },
|
|
461
|
-
capabilities: { experimentalApi: true },
|
|
462
|
-
},
|
|
463
|
-
});
|
|
464
|
-
});
|
|
465
|
-
});
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
function sanitizeSkills(entries) {
|
|
469
|
-
const unique = new Map();
|
|
470
|
-
for (const entry of entries) {
|
|
471
|
-
if (!Array.isArray(entry?.skills)) continue;
|
|
472
|
-
for (const skill of entry.skills) {
|
|
473
|
-
if (
|
|
474
|
-
!skill
|
|
475
|
-
|| typeof skill !== "object"
|
|
476
|
-
|| skill.enabled === false
|
|
477
|
-
|| typeof skill.name !== "string"
|
|
478
|
-
|| !skill.name.trim()
|
|
479
|
-
) {
|
|
480
|
-
continue;
|
|
481
|
-
}
|
|
482
|
-
const id = skill.name.trim();
|
|
483
|
-
if (unique.has(id)) continue;
|
|
484
|
-
const displayName = typeof skill.interface?.displayName === "string"
|
|
485
|
-
? skill.interface.displayName.trim()
|
|
486
|
-
: "";
|
|
487
|
-
unique.set(id, {
|
|
488
|
-
id,
|
|
489
|
-
label: displayName || id,
|
|
490
|
-
description: typeof skill.description === "string" ? skill.description.trim() : "",
|
|
491
|
-
path: typeof skill.path === "string" ? skill.path.trim() : "",
|
|
492
|
-
scope: ["user", "repo", "system", "admin"].includes(skill.scope) ? skill.scope : "user",
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
return [...unique.values()].sort((left, right) => left.label.localeCompare(right.label));
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function sanitizeComposerSkills(entries) {
|
|
500
|
-
const skills = [];
|
|
501
|
-
const identities = new Set();
|
|
502
|
-
for (const entry of entries) {
|
|
503
|
-
if (!Array.isArray(entry?.skills)) continue;
|
|
504
|
-
for (const skill of entry.skills) {
|
|
505
|
-
if (
|
|
506
|
-
!skill
|
|
507
|
-
|| typeof skill !== "object"
|
|
508
|
-
|| skill.enabled !== true
|
|
509
|
-
|| typeof skill.name !== "string"
|
|
510
|
-
|| !skill.name.trim()
|
|
511
|
-
|| typeof skill.path !== "string"
|
|
512
|
-
|| !path.isAbsolute(skill.path.trim())
|
|
513
|
-
) {
|
|
514
|
-
continue;
|
|
515
|
-
}
|
|
516
|
-
const name = skill.name.trim();
|
|
517
|
-
const skillPath = skill.path.trim();
|
|
518
|
-
const identity = `${name}\u0000${skillPath}`;
|
|
519
|
-
if (identities.has(identity)) continue;
|
|
520
|
-
identities.add(identity);
|
|
521
|
-
const displayName = typeof skill.interface?.displayName === "string"
|
|
522
|
-
? skill.interface.displayName.trim()
|
|
523
|
-
: "";
|
|
524
|
-
skills.push({
|
|
525
|
-
identity,
|
|
526
|
-
stableId: name.normalize("NFC"),
|
|
527
|
-
name,
|
|
528
|
-
path: skillPath,
|
|
529
|
-
label: displayName || name,
|
|
530
|
-
description: typeof skill.description === "string" && skill.description.trim()
|
|
531
|
-
? skill.description.trim()
|
|
532
|
-
: null,
|
|
533
|
-
});
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
return skills;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function composerCatalogSignature(skills, agents) {
|
|
540
|
-
return JSON.stringify([...skills, ...agents].map(({
|
|
541
|
-
identity,
|
|
542
|
-
stableId,
|
|
543
|
-
label,
|
|
544
|
-
description,
|
|
545
|
-
developerInstructions,
|
|
546
|
-
}) => ({
|
|
547
|
-
identity,
|
|
548
|
-
stableId,
|
|
549
|
-
label,
|
|
550
|
-
description,
|
|
551
|
-
developerInstructions,
|
|
552
|
-
})));
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function composerSources(skillsAvailable, agentsAvailable = true) {
|
|
556
|
-
return [
|
|
557
|
-
skillsAvailable
|
|
558
|
-
? { kind: "skills", state: "available", reasonCode: null }
|
|
559
|
-
: { kind: "skills", state: "unavailable", reasonCode: "SOURCE_UNAVAILABLE" },
|
|
560
|
-
agentsAvailable
|
|
561
|
-
? { kind: "agents", state: "available", reasonCode: null }
|
|
562
|
-
: { kind: "agents", state: "unavailable", reasonCode: "SOURCE_UNAVAILABLE" },
|
|
563
|
-
{ kind: "slash", state: "available", reasonCode: null },
|
|
564
|
-
...UNSUPPORTED_COMPOSER_SOURCES,
|
|
565
|
-
];
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
export function composerCandidatesForSurface(
|
|
569
|
-
response,
|
|
570
|
-
surface = "ai-chat",
|
|
571
|
-
issueSlashCommands = null,
|
|
572
|
-
query = "",
|
|
573
|
-
) {
|
|
574
|
-
if (surface === "ai-chat") return response;
|
|
575
|
-
if (Array.isArray(issueSlashCommands)) {
|
|
576
|
-
const unique = new Map();
|
|
577
|
-
for (const command of issueSlashCommands) {
|
|
578
|
-
if (
|
|
579
|
-
!command
|
|
580
|
-
|| typeof command.id !== "string"
|
|
581
|
-
|| !/^[a-z][a-z0-9-]*$/.test(command.id)
|
|
582
|
-
|| typeof command.label !== "string"
|
|
583
|
-
|| !command.label.trim()
|
|
584
|
-
|| typeof command.description !== "string"
|
|
585
|
-
|| typeof command.insertText !== "string"
|
|
586
|
-
|| !command.insertText.startsWith(`/${command.id}`)
|
|
587
|
-
|| command.selectable === false
|
|
588
|
-
|| unique.has(command.id)
|
|
589
|
-
) continue;
|
|
590
|
-
unique.set(command.id, command);
|
|
591
|
-
}
|
|
592
|
-
const normalizedQuery = query.toLocaleLowerCase();
|
|
593
|
-
const candidates = [...unique.values()].flatMap((command, itemOrder) => {
|
|
594
|
-
const matchScore = composerMatchScore(
|
|
595
|
-
normalizedQuery,
|
|
596
|
-
[command.id, command.label.replace(/^\//, "")],
|
|
597
|
-
command.description,
|
|
598
|
-
);
|
|
599
|
-
if (matchScore < 0) return [];
|
|
600
|
-
return [{
|
|
601
|
-
kind: "slashAction",
|
|
602
|
-
candidateRef: `slash:insert:${command.id}`,
|
|
603
|
-
trigger: "/",
|
|
604
|
-
label: command.label,
|
|
605
|
-
description: command.description,
|
|
606
|
-
group: "Commands",
|
|
607
|
-
groupOrder: 0,
|
|
608
|
-
itemOrder,
|
|
609
|
-
selectable: true,
|
|
610
|
-
command: `/${command.id}`,
|
|
611
|
-
insertionText: command.insertText,
|
|
612
|
-
selection: { type: "insertText", text: command.insertText },
|
|
613
|
-
matchScore,
|
|
614
|
-
}];
|
|
615
|
-
}).sort((left, right) => (
|
|
616
|
-
right.matchScore - left.matchScore || left.itemOrder - right.itemOrder
|
|
617
|
-
)).map(({ matchScore: _matchScore, ...candidate }) => candidate);
|
|
618
|
-
return { ...response, candidates };
|
|
619
|
-
}
|
|
620
|
-
return {
|
|
621
|
-
...response,
|
|
622
|
-
candidates: response.candidates.map((candidate) => {
|
|
623
|
-
if (candidate.kind !== "slashAction") return candidate;
|
|
624
|
-
const { dispatch: _dispatch, ...persistedCandidate } = candidate;
|
|
625
|
-
return {
|
|
626
|
-
...persistedCandidate,
|
|
627
|
-
selection: { type: "insertText", text: candidate.insertionText },
|
|
628
|
-
};
|
|
629
|
-
}),
|
|
630
|
-
};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
function composerMatchScore(query, primaryValues, description = "") {
|
|
634
|
-
if (!query) return 0;
|
|
635
|
-
const normalizedValues = primaryValues.map((value) => value.toLocaleLowerCase());
|
|
636
|
-
const prefixLengths = normalizedValues
|
|
637
|
-
.filter((value) => value.startsWith(query))
|
|
638
|
-
.map((value) => value.length);
|
|
639
|
-
if (prefixLengths.length > 0) return 1_000 - Math.min(...prefixLengths);
|
|
640
|
-
if (normalizedValues.some((value) => value.includes(query))) return 500;
|
|
641
|
-
return description.toLocaleLowerCase().includes(query) ? 100 : -1;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function referenceUnavailable(nodeIndex, reasonCode = "SOURCE_UNAVAILABLE") {
|
|
645
|
-
return new ApiError(
|
|
646
|
-
409,
|
|
647
|
-
"COMPOSER_REFERENCE_UNAVAILABLE",
|
|
648
|
-
"A selected composer reference is no longer available",
|
|
649
|
-
{ nodeIndex, reasonCode },
|
|
650
|
-
);
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
export class ComposerCatalog {
|
|
654
|
-
constructor({ appServer, agentsDirectory, codexHome, issueSlashCommands, configuredAgents } = {}) {
|
|
655
|
-
this.appServer = appServer;
|
|
656
|
-
this.codexHome = codexHome
|
|
657
|
-
?? (agentsDirectory ? path.dirname(agentsDirectory) : process.env.CODEX_HOME)
|
|
658
|
-
?? path.join(os.homedir(), ".codex");
|
|
659
|
-
this.agentsDirectory = agentsDirectory ?? path.join(this.codexHome, "agents");
|
|
660
|
-
this.issueSlashCommands = issueSlashCommands ?? null;
|
|
661
|
-
this.configuredAgents = configuredAgents ?? listConfiguredAgents;
|
|
662
|
-
this.workspaces = new Map();
|
|
663
|
-
this.unsubscribe = appServer.subscribe((notification) => {
|
|
664
|
-
if (notification?.method === "skills/changed") this.invalidate();
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
async candidatesForSurface(response, { surface, trigger, query }) {
|
|
669
|
-
if (surface === "ai-chat" || trigger !== "/") {
|
|
670
|
-
return composerCandidatesForSurface(response, surface);
|
|
671
|
-
}
|
|
672
|
-
if (!this.issueSlashCommands) {
|
|
673
|
-
return {
|
|
674
|
-
...response,
|
|
675
|
-
candidates: [],
|
|
676
|
-
sources: response.sources.map((source) => (
|
|
677
|
-
source.kind === "slash"
|
|
678
|
-
? { kind: "slash", state: "unavailable", reasonCode: "SOURCE_UNAVAILABLE" }
|
|
679
|
-
: source
|
|
680
|
-
)),
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
try {
|
|
684
|
-
const commands = await this.issueSlashCommands();
|
|
685
|
-
return composerCandidatesForSurface(response, surface, commands, query);
|
|
686
|
-
} catch {
|
|
687
|
-
return {
|
|
688
|
-
...response,
|
|
689
|
-
candidates: [],
|
|
690
|
-
sources: response.sources.map((source) => (
|
|
691
|
-
source.kind === "slash"
|
|
692
|
-
? { kind: "slash", state: "unavailable", reasonCode: "SOURCE_UNAVAILABLE" }
|
|
693
|
-
: source
|
|
694
|
-
)),
|
|
695
|
-
};
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
invalidate() {
|
|
700
|
-
this.workspaces.clear();
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
close() {
|
|
704
|
-
this.unsubscribe();
|
|
705
|
-
this.workspaces.clear();
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
async candidates({ workspacePath, trigger, query }) {
|
|
709
|
-
let entries = [];
|
|
710
|
-
let skillsAvailable = false;
|
|
711
|
-
if (workspacePath) {
|
|
712
|
-
try {
|
|
713
|
-
entries = await this.appServer.listSkills(workspacePath, { forceReload: false });
|
|
714
|
-
skillsAvailable = true;
|
|
715
|
-
} catch {}
|
|
716
|
-
}
|
|
717
|
-
const { agents, available: agentsAvailable } = await this.configuredAgents({
|
|
718
|
-
codexHome: this.codexHome,
|
|
719
|
-
agentsDirectory: this.agentsDirectory,
|
|
720
|
-
workspacePath,
|
|
721
|
-
});
|
|
722
|
-
const workspaceKey = workspacePath ?? "__global__";
|
|
723
|
-
const state = this.#acceptCatalog(workspaceKey, sanitizeComposerSkills(entries), agents);
|
|
724
|
-
const normalizedQuery = query.toLocaleLowerCase();
|
|
725
|
-
const skillIdentityCounts = new Map();
|
|
726
|
-
for (const skill of state.skills) {
|
|
727
|
-
skillIdentityCounts.set(skill.stableId, (skillIdentityCounts.get(skill.stableId) ?? 0) + 1);
|
|
728
|
-
}
|
|
729
|
-
const skillCandidates = trigger === "@" ? state.skills.flatMap((skill, itemOrder) => {
|
|
730
|
-
if (skillIdentityCounts.get(skill.stableId) !== 1) return [];
|
|
731
|
-
const matchScore = composerMatchScore(
|
|
732
|
-
normalizedQuery,
|
|
733
|
-
[skill.label, skill.name],
|
|
734
|
-
skill.description ?? "",
|
|
735
|
-
);
|
|
736
|
-
if (matchScore < 0) return [];
|
|
737
|
-
return [{
|
|
738
|
-
kind: "skill",
|
|
739
|
-
candidateRef: state.refs.get(skill.identity),
|
|
740
|
-
trigger,
|
|
741
|
-
label: skill.label,
|
|
742
|
-
description: skill.description,
|
|
743
|
-
group: "Skills",
|
|
744
|
-
groupOrder: 0,
|
|
745
|
-
itemOrder,
|
|
746
|
-
selectable: true,
|
|
747
|
-
persistence: composerReferencePersistence("skill", skill.stableId, skill.label),
|
|
748
|
-
matchScore,
|
|
749
|
-
}];
|
|
750
|
-
}) : [];
|
|
751
|
-
const agentCandidates = trigger === "@" ? state.agents.flatMap((agent, itemOrder) => {
|
|
752
|
-
const matchScore = composerMatchScore(
|
|
753
|
-
normalizedQuery,
|
|
754
|
-
[agent.label, agent.stableId],
|
|
755
|
-
agent.description ?? "",
|
|
756
|
-
);
|
|
757
|
-
if (matchScore < 0) return [];
|
|
758
|
-
return [{
|
|
759
|
-
kind: "agent",
|
|
760
|
-
candidateRef: state.refs.get(agent.identity),
|
|
761
|
-
trigger,
|
|
762
|
-
label: agent.label,
|
|
763
|
-
description: agent.description,
|
|
764
|
-
group: "Agents",
|
|
765
|
-
groupOrder: 1,
|
|
766
|
-
itemOrder,
|
|
767
|
-
selectable: true,
|
|
768
|
-
insertionText: `@${agent.name}`,
|
|
769
|
-
persistence: composerReferencePersistence("agent", agent.stableId, agent.label),
|
|
770
|
-
matchScore,
|
|
771
|
-
}];
|
|
772
|
-
}) : [];
|
|
773
|
-
const slashCandidates = trigger === "/" ? VERIFIED_SLASH_ACTIONS.flatMap((action, itemOrder) => {
|
|
774
|
-
const matchScore = composerMatchScore(
|
|
775
|
-
normalizedQuery,
|
|
776
|
-
[action.command.slice(1), action.label],
|
|
777
|
-
action.description,
|
|
778
|
-
);
|
|
779
|
-
if (matchScore < 0) return [];
|
|
780
|
-
return [{
|
|
781
|
-
kind: "slashAction",
|
|
782
|
-
candidateRef: `slash:${action.handlerId}`,
|
|
783
|
-
trigger,
|
|
784
|
-
label: action.label,
|
|
785
|
-
description: action.description,
|
|
786
|
-
group: "Commands",
|
|
787
|
-
groupOrder: 0,
|
|
788
|
-
itemOrder,
|
|
789
|
-
selectable: true,
|
|
790
|
-
command: action.command,
|
|
791
|
-
insertionText: action.command,
|
|
792
|
-
dispatch: { type: "client", handlerId: action.handlerId },
|
|
793
|
-
matchScore,
|
|
794
|
-
}];
|
|
795
|
-
}) : [];
|
|
796
|
-
const candidates = [...skillCandidates, ...agentCandidates, ...slashCandidates]
|
|
797
|
-
.sort((left, right) => (
|
|
798
|
-
right.matchScore - left.matchScore
|
|
799
|
-
|| left.groupOrder - right.groupOrder
|
|
800
|
-
|| left.itemOrder - right.itemOrder
|
|
801
|
-
))
|
|
802
|
-
.map(({ matchScore: _matchScore, ...candidate }) => candidate);
|
|
803
|
-
return {
|
|
804
|
-
contractVersion: COMPOSER_CONTRACT_VERSION,
|
|
805
|
-
revision: state.revision,
|
|
806
|
-
candidates,
|
|
807
|
-
sources: composerSources(skillsAvailable, agentsAvailable),
|
|
808
|
-
};
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
async rebindPersistedReferences({ workspacePath, nodes }) {
|
|
812
|
-
let entries = [];
|
|
813
|
-
let skillsAvailable = false;
|
|
814
|
-
try {
|
|
815
|
-
entries = await this.appServer.listSkills(workspacePath, { forceReload: true });
|
|
816
|
-
skillsAvailable = true;
|
|
817
|
-
} catch {}
|
|
818
|
-
const { agents, available: agentsAvailable } = await this.configuredAgents({
|
|
819
|
-
codexHome: this.codexHome,
|
|
820
|
-
agentsDirectory: this.agentsDirectory,
|
|
821
|
-
workspacePath,
|
|
822
|
-
});
|
|
823
|
-
const state = this.#acceptCatalog(
|
|
824
|
-
workspacePath,
|
|
825
|
-
sanitizeComposerSkills(entries),
|
|
826
|
-
agents,
|
|
827
|
-
);
|
|
828
|
-
const sources = composerSources(skillsAvailable, agentsAvailable);
|
|
829
|
-
const byStableIdentity = new Map();
|
|
830
|
-
for (const item of state.skills) {
|
|
831
|
-
const key = `skill\u0000${item.stableId}`;
|
|
832
|
-
const matches = byStableIdentity.get(key) ?? [];
|
|
833
|
-
matches.push(item);
|
|
834
|
-
byStableIdentity.set(key, matches);
|
|
835
|
-
}
|
|
836
|
-
for (const item of state.agents) {
|
|
837
|
-
const key = `agent\u0000${item.stableId}`;
|
|
838
|
-
const matches = byStableIdentity.get(key) ?? [];
|
|
839
|
-
matches.push(item);
|
|
840
|
-
byStableIdentity.set(key, matches);
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
const reboundNodes = [];
|
|
844
|
-
const bindings = [];
|
|
845
|
-
let ready = true;
|
|
846
|
-
for (const [nodeIndex, node] of nodes.entries()) {
|
|
847
|
-
if (node.type === "text") {
|
|
848
|
-
reboundNodes.push(node);
|
|
849
|
-
continue;
|
|
850
|
-
}
|
|
851
|
-
if (node.type === "unsupportedReference") {
|
|
852
|
-
ready = false;
|
|
853
|
-
bindings.push({
|
|
854
|
-
nodeIndex,
|
|
855
|
-
status: "unavailable",
|
|
856
|
-
referenceKind: "unsupported",
|
|
857
|
-
reasonCode: node.reasonCode,
|
|
858
|
-
});
|
|
859
|
-
continue;
|
|
860
|
-
}
|
|
861
|
-
const matches = byStableIdentity.get(`${node.referenceKind}\u0000${node.stableId}`) ?? [];
|
|
862
|
-
let reasonCode = null;
|
|
863
|
-
if (node.referenceKind === "skill" && !skillsAvailable) {
|
|
864
|
-
reasonCode = "SOURCE_UNAVAILABLE";
|
|
865
|
-
} else if (node.referenceKind === "agent" && !agentsAvailable) {
|
|
866
|
-
reasonCode = "SOURCE_UNAVAILABLE";
|
|
867
|
-
} else if (matches.length === 0) {
|
|
868
|
-
reasonCode = "REFERENCE_NOT_FOUND";
|
|
869
|
-
} else if (matches.length > 1) {
|
|
870
|
-
reasonCode = "REFERENCE_AMBIGUOUS";
|
|
871
|
-
}
|
|
872
|
-
if (reasonCode) {
|
|
873
|
-
ready = false;
|
|
874
|
-
bindings.push({
|
|
875
|
-
nodeIndex,
|
|
876
|
-
status: "unavailable",
|
|
877
|
-
referenceKind: node.referenceKind,
|
|
878
|
-
reasonCode,
|
|
879
|
-
});
|
|
880
|
-
continue;
|
|
881
|
-
}
|
|
882
|
-
const reference = matches[0];
|
|
883
|
-
bindings.push({
|
|
884
|
-
nodeIndex,
|
|
885
|
-
status: "resolved",
|
|
886
|
-
referenceKind: node.referenceKind,
|
|
887
|
-
label: reference.label,
|
|
888
|
-
});
|
|
889
|
-
reboundNodes.push({
|
|
890
|
-
type: node.referenceKind,
|
|
891
|
-
candidateRef: state.refs.get(reference.identity),
|
|
892
|
-
label: reference.label,
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
return {
|
|
896
|
-
contractVersion: COMPOSER_CONTRACT_VERSION,
|
|
897
|
-
ready,
|
|
898
|
-
revision: state.revision,
|
|
899
|
-
...(ready ? { document: { version: 1, nodes: reboundNodes } } : {}),
|
|
900
|
-
bindings,
|
|
901
|
-
sources,
|
|
902
|
-
diagnostics: [],
|
|
903
|
-
};
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
async resolveReferences({ workspacePath, revision, nodes }) {
|
|
907
|
-
const previous = this.workspaces.get(workspacePath);
|
|
908
|
-
if (!previous || previous.revision !== revision) {
|
|
909
|
-
const firstReferenceIndex = nodes.findIndex((node) => (
|
|
910
|
-
node.type === "skill" || node.type === "agent"
|
|
911
|
-
));
|
|
912
|
-
throw referenceUnavailable(Math.max(firstReferenceIndex, 0));
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
let entries;
|
|
916
|
-
try {
|
|
917
|
-
entries = await this.appServer.listSkills(workspacePath, { forceReload: true });
|
|
918
|
-
} catch {
|
|
919
|
-
const firstReferenceIndex = nodes.findIndex((node) => (
|
|
920
|
-
node.type === "skill" || node.type === "agent"
|
|
921
|
-
));
|
|
922
|
-
throw referenceUnavailable(Math.max(firstReferenceIndex, 0));
|
|
923
|
-
}
|
|
924
|
-
const { agents } = await this.configuredAgents({
|
|
925
|
-
codexHome: this.codexHome,
|
|
926
|
-
agentsDirectory: this.agentsDirectory,
|
|
927
|
-
workspacePath,
|
|
928
|
-
});
|
|
929
|
-
const current = this.#acceptCatalog(workspacePath, sanitizeComposerSkills(entries), agents);
|
|
930
|
-
if (current.revision !== revision) {
|
|
931
|
-
const firstReferenceIndex = nodes.findIndex((node) => (
|
|
932
|
-
node.type === "skill" || node.type === "agent"
|
|
933
|
-
));
|
|
934
|
-
throw referenceUnavailable(Math.max(firstReferenceIndex, 0));
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
const byRef = new Map([...current.skills, ...current.agents].map((item) => (
|
|
938
|
-
[current.refs.get(item.identity), item]
|
|
939
|
-
)));
|
|
940
|
-
return nodes.map((node, nodeIndex) => {
|
|
941
|
-
if (node.type !== "skill" && node.type !== "agent") return null;
|
|
942
|
-
const reference = byRef.get(node.candidateRef);
|
|
943
|
-
if (!reference) throw referenceUnavailable(nodeIndex);
|
|
944
|
-
return reference;
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
async resolveSkills(options) {
|
|
949
|
-
return this.resolveReferences(options);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
#acceptCatalog(workspacePath, skills, agents) {
|
|
953
|
-
const signature = composerCatalogSignature(skills, agents);
|
|
954
|
-
const current = this.workspaces.get(workspacePath);
|
|
955
|
-
if (current?.signature === signature) return current;
|
|
956
|
-
const refs = new Map([...skills, ...agents].map((item) => [item.identity, randomUUID()]));
|
|
957
|
-
const state = { revision: randomUUID(), signature, skills, agents, refs };
|
|
958
|
-
this.workspaces.set(workspacePath, state);
|
|
959
|
-
return state;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
export async function loadSlashCommands(platform = process.platform) {
|
|
964
|
-
slashCommandCatalogPromise ??= readFile(SLASH_COMMAND_CATALOG_URL, "utf8")
|
|
965
|
-
.then((source) => JSON.parse(source));
|
|
966
|
-
const catalog = await slashCommandCatalogPromise;
|
|
967
|
-
if (!Array.isArray(catalog?.commands)) {
|
|
968
|
-
throw new Error("Codex slash command catalog is invalid");
|
|
969
|
-
}
|
|
970
|
-
return catalog.commands.flatMap((command) => {
|
|
971
|
-
if (
|
|
972
|
-
!command
|
|
973
|
-
|| typeof command.id !== "string"
|
|
974
|
-
|| !/^[a-z][a-z0-9-]*$/.test(command.id)
|
|
975
|
-
|| typeof command.description !== "string"
|
|
976
|
-
|| command.debugOnly === true
|
|
977
|
-
|| (Array.isArray(command.platforms) && !command.platforms.includes(platform))
|
|
978
|
-
|| (Array.isArray(command.excludedPlatforms) && command.excludedPlatforms.includes(platform))
|
|
979
|
-
) {
|
|
980
|
-
return [];
|
|
981
|
-
}
|
|
982
|
-
return [{
|
|
983
|
-
id: command.id,
|
|
984
|
-
label: `/${command.id}`,
|
|
985
|
-
description: command.description,
|
|
986
|
-
insertText: `/${command.id}${command.supportsInlineArgs === true ? " " : ""}`,
|
|
987
|
-
}];
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
export async function discoverAiCatalog({
|
|
992
|
-
codexExecutable,
|
|
993
|
-
workspacePath,
|
|
994
|
-
processEnv,
|
|
995
|
-
}) {
|
|
996
|
-
const environment = withoutTaskboardLauncherEnvironment(processEnv);
|
|
997
|
-
const modelCommand = executableCommand(codexExecutable, ["debug", "models"]);
|
|
998
|
-
const [modelResult, skillEntries, commands] = await Promise.all([
|
|
999
|
-
execFileAsync(modelCommand.executable, modelCommand.args, {
|
|
1000
|
-
cwd: workspacePath,
|
|
1001
|
-
env: environment,
|
|
1002
|
-
encoding: "utf8",
|
|
1003
|
-
timeout: CATALOG_TIMEOUT_MS,
|
|
1004
|
-
maxBuffer: CATALOG_MAX_BUFFER,
|
|
1005
|
-
windowsHide: true,
|
|
1006
|
-
}),
|
|
1007
|
-
listSkills(codexExecutable, workspacePath, environment),
|
|
1008
|
-
loadSlashCommands(),
|
|
1009
|
-
]);
|
|
1010
|
-
const modelCatalog = JSON.parse(modelResult.stdout);
|
|
1011
|
-
return {
|
|
1012
|
-
models: sanitizeModels(modelCatalog?.models),
|
|
1013
|
-
skills: sanitizeSkills(skillEntries),
|
|
1014
|
-
commands,
|
|
1015
|
-
sandboxes: ["read-only", "workspace-write", "danger-full-access"],
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
export async function discoverAppServerAiCatalog({ appServer, workspacePath }) {
|
|
1020
|
-
const [modelResult, skillEntries, commands] = await Promise.all([
|
|
1021
|
-
appServer.request("model/list", { cursor: null, limit: 100, includeHidden: false }),
|
|
1022
|
-
appServer.listSkills(workspacePath, { forceReload: false }),
|
|
1023
|
-
loadSlashCommands(),
|
|
1024
|
-
]);
|
|
1025
|
-
return {
|
|
1026
|
-
models: sanitizeAppServerModels(modelResult?.data),
|
|
1027
|
-
skills: sanitizeSkills(skillEntries),
|
|
1028
|
-
commands,
|
|
1029
|
-
sandboxes: ["read-only", "workspace-write", "danger-full-access"],
|
|
1030
|
-
};
|
|
1031
|
-
}
|