@co0ontty/wand 4.55.3 → 4.56.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/github-connector.d.ts +22 -0
- package/dist/github-connector.js +117 -0
- package/dist/missions.js +42 -9
- package/dist/server-github-routes.d.ts +10 -0
- package/dist/server-github-routes.js +239 -0
- package/dist/server-settings-routes.d.ts +2 -0
- package/dist/server-settings-routes.js +1 -0
- package/dist/server-task-routes.d.ts +4 -0
- package/dist/server-task-routes.js +99 -0
- package/dist/server-workspace-routes.js +70 -27
- package/dist/server.js +25 -0
- package/dist/storage.d.ts +42 -0
- package/dist/storage.js +223 -0
- package/dist/structured-provider-common.js +2 -1
- package/dist/task-types.d.ts +19 -0
- package/dist/task-types.js +1 -0
- package/dist/taskboard-bridge.d.ts +11 -0
- package/dist/taskboard-bridge.js +360 -0
- package/dist/web-ui/content/scripts.js +123 -127
- package/dist/web-ui/content/styles.css +1 -1
- package/dist/web-ui/embedded-assets.js +2 -2
- package/dist/web-ui/index.js +1 -0
- package/package.json +4 -2
- package/vendor/codex-taskboard/LICENSE +201 -0
- package/vendor/codex-taskboard/server/ai-chat-catalog.mjs +1031 -0
- package/vendor/codex-taskboard/server/ai-chat-process.mjs +462 -0
- package/vendor/codex-taskboard/server/ai-chat.mjs +1237 -0
- package/vendor/codex-taskboard/server/ai-turn-owner.mjs +39 -0
- package/vendor/codex-taskboard/server/app.mjs +3211 -0
- package/vendor/codex-taskboard/server/cloud-config.mjs +188 -0
- package/vendor/codex-taskboard/server/cloud-proxy.mjs +310 -0
- package/vendor/codex-taskboard/server/codex-app-server.mjs +376 -0
- package/vendor/codex-taskboard/server/codex-slash-commands-0.139.0.json +62 -0
- package/vendor/codex-taskboard/server/database.mjs +2845 -0
- package/vendor/codex-taskboard/server/index.mjs +41 -0
- package/vendor/codex-taskboard/server/jira-config.mjs +181 -0
- package/vendor/codex-taskboard/server/jira-integration.mjs +487 -0
- package/vendor/codex-taskboard/server/project-summary.mjs +159 -0
- package/vendor/codex-taskboard/shared/api-fields.mjs +163 -0
- package/vendor/codex-taskboard/shared/codex-environment.mjs +5 -0
- package/vendor/codex-taskboard/shared/codex-executable.mjs +77 -0
- package/vendor/codex-taskboard/shared/codex-permissions.mjs +9 -0
- package/vendor/codex-taskboard/shared/composer-reference.d.mts +19 -0
- package/vendor/codex-taskboard/shared/composer-reference.mjs +90 -0
- package/vendor/codex-taskboard/shared/domain.mjs +34 -0
- package/vendor/codex-taskboard/shared/executable-command.mjs +13 -0
- package/vendor/codex-taskboard/shared/process-tree.mjs +27 -0
- package/vendor/codex-taskboard/shared/task-input.mjs +308 -0
- package/vendor/codex-taskboard/shared/task-records.mjs +199 -0
- package/vendor/codex-taskboard/shared/task-relations.mjs +55 -0
- package/vendor/codex-taskboard/shared/taskboard-automation.mjs +277 -0
- package/vendor/codex-taskboard/skills/manage-taskboard/SKILL.md +45 -0
- package/vendor/codex-taskboard/skills/manage-taskboard/agents/openai.yaml +4 -0
- package/vendor/codex-taskboard/skills/manage-taskboard/references/cli.md +211 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { createTaskboardServer, resolveHost, resolvePort } from "./app.mjs";
|
|
5
|
+
|
|
6
|
+
export { createTaskboardServer, resolveHost, resolvePort, resolveServerOptions } from "./app.mjs";
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
const app = createTaskboardServer();
|
|
10
|
+
const host = resolveHost();
|
|
11
|
+
const listenFd = process.env.CODEX_TASKBOARD_LISTEN_FD === undefined
|
|
12
|
+
? null
|
|
13
|
+
: Number(process.env.CODEX_TASKBOARD_LISTEN_FD);
|
|
14
|
+
const address = await app.listen({ host, port: resolvePort(), fd: listenFd });
|
|
15
|
+
console.log(`Codex Taskboard listening on http://127.0.0.1:${address.port}`);
|
|
16
|
+
if (host === "0.0.0.0") {
|
|
17
|
+
const addresses = Object.values(os.networkInterfaces())
|
|
18
|
+
.flat()
|
|
19
|
+
.filter((entry) => entry?.family === "IPv4" && !entry.internal)
|
|
20
|
+
.map((entry) => entry.address);
|
|
21
|
+
for (const lanAddress of [...new Set(addresses)]) {
|
|
22
|
+
console.log(`Codex Taskboard available on LAN at http://${lanAddress}:${address.port}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let closing = false;
|
|
27
|
+
const close = async () => {
|
|
28
|
+
if (closing) return;
|
|
29
|
+
closing = true;
|
|
30
|
+
await app.close();
|
|
31
|
+
};
|
|
32
|
+
process.once("SIGINT", () => close().then(() => process.exit(0)));
|
|
33
|
+
process.once("SIGTERM", () => close().then(() => process.exit(0)));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
37
|
+
main().catch((error) => {
|
|
38
|
+
console.error(error);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const CONFIG_VERSION = 2;
|
|
5
|
+
const LEGACY_CONFIG_VERSION = 1;
|
|
6
|
+
|
|
7
|
+
export class JiraConfigError extends Error {
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "JiraConfigError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function normalizeJiraUrl(value) {
|
|
16
|
+
if (typeof value !== "string" || value.includes("?") || value.includes("#")) {
|
|
17
|
+
throw new JiraConfigError(
|
|
18
|
+
"INVALID_JIRA_URL",
|
|
19
|
+
"Jira 地址必须使用 http 或 https,且不能包含账号、查询参数或片段",
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
let url;
|
|
23
|
+
try {
|
|
24
|
+
url = new URL(value);
|
|
25
|
+
} catch {
|
|
26
|
+
throw new JiraConfigError("INVALID_JIRA_URL", "Jira 地址无效");
|
|
27
|
+
}
|
|
28
|
+
if (
|
|
29
|
+
(url.protocol !== "http:" && url.protocol !== "https:")
|
|
30
|
+
|| url.username
|
|
31
|
+
|| url.password
|
|
32
|
+
|| url.search
|
|
33
|
+
|| url.hash
|
|
34
|
+
) {
|
|
35
|
+
throw new JiraConfigError(
|
|
36
|
+
"INVALID_JIRA_URL",
|
|
37
|
+
"Jira 地址必须使用 http 或 https,且不能包含账号、查询参数或片段",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
url.pathname = url.pathname.replace(/\/+$/, "");
|
|
41
|
+
return url.toString().replace(/\/$/, "");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function validateCredentials(username, password) {
|
|
45
|
+
if (
|
|
46
|
+
typeof username !== "string"
|
|
47
|
+
|| !username.trim()
|
|
48
|
+
|| username.length > 254
|
|
49
|
+
|| username.includes(":")
|
|
50
|
+
) {
|
|
51
|
+
throw new JiraConfigError(
|
|
52
|
+
"INVALID_JIRA_USERNAME",
|
|
53
|
+
"Jira 用户名不能为空、不能包含冒号,且不能超过 254 个字符",
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (typeof password !== "string" || !password || password.length > 4096) {
|
|
57
|
+
throw new JiraConfigError(
|
|
58
|
+
"INVALID_JIRA_PASSWORD",
|
|
59
|
+
"Jira 密码不能为空且不能超过 4096 个字符",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return { username: username.trim(), password };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function validateProjects(value) {
|
|
66
|
+
if (value === undefined) return [];
|
|
67
|
+
if (!Array.isArray(value) || value.length > 20) {
|
|
68
|
+
throw new JiraConfigError("INVALID_JIRA_PROJECTS", "Jira 项目必须是最多 20 项的数组");
|
|
69
|
+
}
|
|
70
|
+
const projects = value.map((project) => {
|
|
71
|
+
if (
|
|
72
|
+
typeof project !== "string"
|
|
73
|
+
|| !project.trim()
|
|
74
|
+
|| project.length > 128
|
|
75
|
+
|| /[\u0000-\u001f\u007f]/.test(project)
|
|
76
|
+
) {
|
|
77
|
+
throw new JiraConfigError(
|
|
78
|
+
"INVALID_JIRA_PROJECTS",
|
|
79
|
+
"Jira 项目名称或 Key 不能为空、不能包含控制字符,且不能超过 128 个字符",
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
return project.trim();
|
|
83
|
+
});
|
|
84
|
+
return [...new Set(projects)];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function parseConfig(value) {
|
|
88
|
+
if (
|
|
89
|
+
value === null
|
|
90
|
+
|| typeof value !== "object"
|
|
91
|
+
|| Array.isArray(value)
|
|
92
|
+
|| (value.version !== LEGACY_CONFIG_VERSION && value.version !== CONFIG_VERSION)
|
|
93
|
+
) {
|
|
94
|
+
throw new JiraConfigError("INVALID_JIRA_CONFIG", "Jira 配置文件无效");
|
|
95
|
+
}
|
|
96
|
+
const allowedKeys = new Set([
|
|
97
|
+
"version",
|
|
98
|
+
"baseUrl",
|
|
99
|
+
"username",
|
|
100
|
+
"password",
|
|
101
|
+
"originId",
|
|
102
|
+
"displayName",
|
|
103
|
+
"projects",
|
|
104
|
+
]);
|
|
105
|
+
if (value.version === LEGACY_CONFIG_VERSION) allowedKeys.delete("originId");
|
|
106
|
+
if (Object.keys(value).some((key) => !allowedKeys.has(key))) {
|
|
107
|
+
throw new JiraConfigError("INVALID_JIRA_CONFIG", "Jira 配置文件包含未知字段");
|
|
108
|
+
}
|
|
109
|
+
const credentials = validateCredentials(value.username, value.password);
|
|
110
|
+
if (
|
|
111
|
+
value.version === CONFIG_VERSION
|
|
112
|
+
&& (typeof value.originId !== "string" || !/^[a-f0-9]{64}$/.test(value.originId))
|
|
113
|
+
) {
|
|
114
|
+
throw new JiraConfigError("INVALID_JIRA_CONFIG", "Jira 配置缺少稳定实例身份");
|
|
115
|
+
}
|
|
116
|
+
if (typeof value.displayName !== "string" || !value.displayName.trim()) {
|
|
117
|
+
throw new JiraConfigError("INVALID_JIRA_CONFIG", "Jira 配置缺少用户显示名称");
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
version: value.version,
|
|
121
|
+
baseUrl: normalizeJiraUrl(value.baseUrl),
|
|
122
|
+
...credentials,
|
|
123
|
+
...(value.version === CONFIG_VERSION ? { originId: value.originId } : {}),
|
|
124
|
+
displayName: value.displayName.trim().slice(0, 254),
|
|
125
|
+
projects: validateProjects(value.projects),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function createJiraConfigStore({ configPath }) {
|
|
130
|
+
if (!configPath) throw new Error("configPath is required");
|
|
131
|
+
let pendingWrite = Promise.resolve();
|
|
132
|
+
|
|
133
|
+
async function readFromDisk() {
|
|
134
|
+
try {
|
|
135
|
+
return parseConfig(JSON.parse(await readFile(configPath, "utf8")));
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (error?.code === "ENOENT") return null;
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function writeAtomically(config) {
|
|
143
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
144
|
+
const temporaryPath = `${configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
145
|
+
await writeFile(temporaryPath, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
146
|
+
await chmod(temporaryPath, 0o600);
|
|
147
|
+
await rename(temporaryPath, configPath);
|
|
148
|
+
await chmod(configPath, 0o600);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
async read() {
|
|
153
|
+
await pendingWrite;
|
|
154
|
+
return readFromDisk();
|
|
155
|
+
},
|
|
156
|
+
async save(input) {
|
|
157
|
+
const config = parseConfig({ ...input, version: CONFIG_VERSION });
|
|
158
|
+
const operation = pendingWrite.catch(() => {}).then(async () => {
|
|
159
|
+
await writeAtomically(config);
|
|
160
|
+
return config;
|
|
161
|
+
});
|
|
162
|
+
pendingWrite = operation.catch(() => {});
|
|
163
|
+
return operation;
|
|
164
|
+
},
|
|
165
|
+
async clear() {
|
|
166
|
+
await pendingWrite;
|
|
167
|
+
try {
|
|
168
|
+
await unlink(configPath);
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (error?.code !== "ENOENT") throw error;
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
validate({ baseUrl, username, password, projects }) {
|
|
174
|
+
return {
|
|
175
|
+
baseUrl: normalizeJiraUrl(baseUrl),
|
|
176
|
+
...validateCredentials(username, password),
|
|
177
|
+
projects: validateProjects(projects),
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|