workplane 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/cli.js +188 -73
- package/dist/cli.js.map +1 -1
- package/dist/migrate.js +14 -14
- package/dist/migrate.js.map +1 -1
- package/dist/node.js +40 -40
- package/dist/node.js.map +1 -1
- package/dist/server.js +254 -140
- package/dist/server.js.map +1 -1
- package/dist/setup.js +14 -14
- package/dist/setup.js.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// ../../libs/workplans/src/providers/anthropic.ts
|
|
13
13
|
var anthropic_exports = {};
|
|
14
14
|
__export(anthropic_exports, {
|
|
15
15
|
runAnthropicStep: () => runAnthropicStep
|
|
@@ -26,12 +26,12 @@ async function runAnthropicStep(opts) {
|
|
|
26
26
|
return block.type === "text" ? block.text : "";
|
|
27
27
|
}
|
|
28
28
|
var init_anthropic = __esm({
|
|
29
|
-
"
|
|
29
|
+
"../../libs/workplans/src/providers/anthropic.ts"() {
|
|
30
30
|
"use strict";
|
|
31
31
|
}
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
//
|
|
34
|
+
// ../../libs/workplans/src/providers/openai.ts
|
|
35
35
|
var openai_exports = {};
|
|
36
36
|
__export(openai_exports, {
|
|
37
37
|
runOpenAIStep: () => runOpenAIStep
|
|
@@ -46,12 +46,12 @@ async function runOpenAIStep(opts) {
|
|
|
46
46
|
return completion.choices[0]?.message.content ?? "";
|
|
47
47
|
}
|
|
48
48
|
var init_openai = __esm({
|
|
49
|
-
"
|
|
49
|
+
"../../libs/workplans/src/providers/openai.ts"() {
|
|
50
50
|
"use strict";
|
|
51
51
|
}
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
//
|
|
54
|
+
// ../../libs/workplans/src/providers/ollama.ts
|
|
55
55
|
var ollama_exports = {};
|
|
56
56
|
__export(ollama_exports, {
|
|
57
57
|
runOllamaStep: () => runOllamaStep
|
|
@@ -64,12 +64,12 @@ async function runOllamaStep(opts) {
|
|
|
64
64
|
return response.response;
|
|
65
65
|
}
|
|
66
66
|
var init_ollama = __esm({
|
|
67
|
-
"
|
|
67
|
+
"../../libs/workplans/src/providers/ollama.ts"() {
|
|
68
68
|
"use strict";
|
|
69
69
|
}
|
|
70
70
|
});
|
|
71
71
|
|
|
72
|
-
//
|
|
72
|
+
// ../../libs/workplans/src/providers/shell.ts
|
|
73
73
|
var shell_exports = {};
|
|
74
74
|
__export(shell_exports, {
|
|
75
75
|
runShellStep: () => runShellStep
|
|
@@ -85,13 +85,13 @@ async function runShellStep(opts) {
|
|
|
85
85
|
}
|
|
86
86
|
var execAsync;
|
|
87
87
|
var init_shell = __esm({
|
|
88
|
-
"
|
|
88
|
+
"../../libs/workplans/src/providers/shell.ts"() {
|
|
89
89
|
"use strict";
|
|
90
90
|
execAsync = promisify(exec);
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
-
//
|
|
94
|
+
// ../../libs/workplans/src/providers/file.ts
|
|
95
95
|
var file_exports = {};
|
|
96
96
|
__export(file_exports, {
|
|
97
97
|
runFileStep: () => runFileStep
|
|
@@ -101,12 +101,12 @@ async function runFileStep(opts) {
|
|
|
101
101
|
return readFile(opts.path, opts.encoding ?? "utf8");
|
|
102
102
|
}
|
|
103
103
|
var init_file = __esm({
|
|
104
|
-
"
|
|
104
|
+
"../../libs/workplans/src/providers/file.ts"() {
|
|
105
105
|
"use strict";
|
|
106
106
|
}
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
-
//
|
|
109
|
+
// ../../libs/dbos/src/scheduler.ts
|
|
110
110
|
var scheduler_exports = {};
|
|
111
111
|
__export(scheduler_exports, {
|
|
112
112
|
registerDbosSchedulerTick: () => registerDbosSchedulerTick
|
|
@@ -126,13 +126,13 @@ function registerDbosSchedulerTick(scheduler) {
|
|
|
126
126
|
}
|
|
127
127
|
var registered;
|
|
128
128
|
var init_scheduler = __esm({
|
|
129
|
-
"
|
|
129
|
+
"../../libs/dbos/src/scheduler.ts"() {
|
|
130
130
|
"use strict";
|
|
131
131
|
registered = false;
|
|
132
132
|
}
|
|
133
133
|
});
|
|
134
134
|
|
|
135
|
-
//
|
|
135
|
+
// ../../libs/dbos/src/index.ts
|
|
136
136
|
var src_exports = {};
|
|
137
137
|
__export(src_exports, {
|
|
138
138
|
DbosWorkflows: () => DbosWorkflows,
|
|
@@ -145,7 +145,7 @@ function createDbosWorkflows(store) {
|
|
|
145
145
|
}
|
|
146
146
|
var DbosWorkflows;
|
|
147
147
|
var init_src = __esm({
|
|
148
|
-
"
|
|
148
|
+
"../../libs/dbos/src/index.ts"() {
|
|
149
149
|
"use strict";
|
|
150
150
|
init_scheduler();
|
|
151
151
|
DbosWorkflows = class {
|
|
@@ -248,10 +248,33 @@ import { createServer } from "http";
|
|
|
248
248
|
import { URL } from "url";
|
|
249
249
|
import { ZodError } from "zod";
|
|
250
250
|
|
|
251
|
-
//
|
|
251
|
+
// ../../libs/core/src/auth.ts
|
|
252
|
+
function parseBearerToken(authorizationHeader) {
|
|
253
|
+
if (!authorizationHeader) {
|
|
254
|
+
return void 0;
|
|
255
|
+
}
|
|
256
|
+
const match = /^Bearer\s+(.+)$/i.exec(authorizationHeader.trim());
|
|
257
|
+
return match?.[1];
|
|
258
|
+
}
|
|
259
|
+
function isAuthorizedBearer(provided, expected) {
|
|
260
|
+
if (!expected) {
|
|
261
|
+
return true;
|
|
262
|
+
}
|
|
263
|
+
if (!provided) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
return provided === expected;
|
|
267
|
+
}
|
|
268
|
+
function getRequestBearer(req) {
|
|
269
|
+
const header = req.headers.authorization;
|
|
270
|
+
const value = Array.isArray(header) ? header[0] : header;
|
|
271
|
+
return parseBearerToken(value);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ../../libs/core/src/config.ts
|
|
252
275
|
import { z } from "zod";
|
|
253
276
|
|
|
254
|
-
//
|
|
277
|
+
// ../../libs/core/src/env.ts
|
|
255
278
|
import { config as dotenvConfig } from "dotenv";
|
|
256
279
|
import { homedir } from "os";
|
|
257
280
|
import { join } from "path";
|
|
@@ -266,7 +289,7 @@ function loadLocalEnv() {
|
|
|
266
289
|
envLoaded = true;
|
|
267
290
|
}
|
|
268
291
|
|
|
269
|
-
//
|
|
292
|
+
// ../../libs/core/src/config.ts
|
|
270
293
|
loadLocalEnv();
|
|
271
294
|
var serverEnvSchema = z.object({
|
|
272
295
|
DATABASE_URL: z.string().optional().default("sqlite://~/.workplane/workplane.db"),
|
|
@@ -298,7 +321,13 @@ function parseCsv(value) {
|
|
|
298
321
|
return value.split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
299
322
|
}
|
|
300
323
|
|
|
301
|
-
//
|
|
324
|
+
// ../../libs/core/src/ids.ts
|
|
325
|
+
import { randomUUID } from "crypto";
|
|
326
|
+
function makeId(prefix) {
|
|
327
|
+
return `${prefix}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ../../libs/db/src/client.ts
|
|
302
331
|
import { mkdirSync } from "fs";
|
|
303
332
|
import { homedir as homedir2 } from "os";
|
|
304
333
|
import { dirname, join as join2 } from "path";
|
|
@@ -307,16 +336,10 @@ import { drizzle as drizzleSqlite } from "drizzle-orm/better-sqlite3";
|
|
|
307
336
|
import Database from "better-sqlite3";
|
|
308
337
|
import { Pool } from "pg";
|
|
309
338
|
|
|
310
|
-
//
|
|
339
|
+
// ../../libs/db/src/pg-store.ts
|
|
311
340
|
import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql as sql2 } from "drizzle-orm";
|
|
312
341
|
|
|
313
|
-
//
|
|
314
|
-
import { randomUUID } from "crypto";
|
|
315
|
-
function makeId(prefix) {
|
|
316
|
-
return `${prefix}_${randomUUID().replace(/-/g, "").slice(0, 12)}`;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// ../db/src/schema/pg.ts
|
|
342
|
+
// ../../libs/db/src/schema/pg.ts
|
|
320
343
|
var pg_exports = {};
|
|
321
344
|
__export(pg_exports, {
|
|
322
345
|
artifacts: () => artifacts,
|
|
@@ -444,7 +467,7 @@ var runInputEvents = pgTable("run_input_events", {
|
|
|
444
467
|
undeliveredIdx: index("idx_run_input_events_undelivered").on(table.runId, table.sequence).where(isNull(table.deliveredAt))
|
|
445
468
|
}));
|
|
446
469
|
|
|
447
|
-
//
|
|
470
|
+
// ../../libs/db/src/pg-store.ts
|
|
448
471
|
function toTaskRecord(row) {
|
|
449
472
|
return {
|
|
450
473
|
id: row.id,
|
|
@@ -616,6 +639,10 @@ var PgStore = class {
|
|
|
616
639
|
}).returning();
|
|
617
640
|
return toNodeRecord(inserted);
|
|
618
641
|
}
|
|
642
|
+
async listNodes() {
|
|
643
|
+
const rows = await this.db.select().from(nodes).orderBy(asc(nodes.name));
|
|
644
|
+
return rows.map(toNodeRecord);
|
|
645
|
+
}
|
|
619
646
|
async getRunCancellationState(runId) {
|
|
620
647
|
const [row] = await this.db.select({ runStatus: runs.status, taskStatus: tasks.status }).from(runs).innerJoin(tasks, eq(tasks.id, runs.taskId)).where(eq(runs.id, runId));
|
|
621
648
|
return row ?? null;
|
|
@@ -686,8 +713,8 @@ var PgStore = class {
|
|
|
686
713
|
);
|
|
687
714
|
return logs.length;
|
|
688
715
|
}
|
|
689
|
-
async getRunLogs(runId) {
|
|
690
|
-
const rows = await this.db.select().from(runLogs).where(eq(runLogs.runId, runId)).orderBy(asc(runLogs.timestamp));
|
|
716
|
+
async getRunLogs(runId, afterId) {
|
|
717
|
+
const rows = await this.db.select().from(runLogs).where(afterId === void 0 ? eq(runLogs.runId, runId) : and(eq(runLogs.runId, runId), gt(runLogs.id, afterId))).orderBy(asc(runLogs.timestamp), asc(runLogs.id));
|
|
691
718
|
return rows.map(toLogRecord);
|
|
692
719
|
}
|
|
693
720
|
async createArtifact(runId, input) {
|
|
@@ -872,10 +899,10 @@ var PgStore = class {
|
|
|
872
899
|
}
|
|
873
900
|
};
|
|
874
901
|
|
|
875
|
-
//
|
|
902
|
+
// ../../libs/db/src/sqlite-store.ts
|
|
876
903
|
import { and as and2, asc as asc2, desc as desc2, eq as eq2, gt as gt2, lte as lte2, sql as sql4 } from "drizzle-orm";
|
|
877
904
|
|
|
878
|
-
//
|
|
905
|
+
// ../../libs/db/src/schema/sqlite.ts
|
|
879
906
|
var sqlite_exports = {};
|
|
880
907
|
__export(sqlite_exports, {
|
|
881
908
|
artifacts: () => artifacts2,
|
|
@@ -1004,7 +1031,7 @@ var runInputEvents2 = sqliteTable("run_input_events", {
|
|
|
1004
1031
|
undeliveredIdx: index2("idx_run_input_events_undelivered").on(table.runId, table.sequence).where(isNull2(table.deliveredAt))
|
|
1005
1032
|
}));
|
|
1006
1033
|
|
|
1007
|
-
//
|
|
1034
|
+
// ../../libs/db/src/sqlite-store.ts
|
|
1008
1035
|
function nowIso() {
|
|
1009
1036
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1010
1037
|
}
|
|
@@ -1195,6 +1222,10 @@ var SqliteStore = class {
|
|
|
1195
1222
|
}).returning();
|
|
1196
1223
|
return toNodeRecord2(inserted);
|
|
1197
1224
|
}
|
|
1225
|
+
async listNodes() {
|
|
1226
|
+
const rows = await this.db.select().from(nodes2).orderBy(asc2(nodes2.name));
|
|
1227
|
+
return rows.map(toNodeRecord2);
|
|
1228
|
+
}
|
|
1198
1229
|
async getRunCancellationState(runId) {
|
|
1199
1230
|
const [row] = await this.db.select({ runStatus: runs2.status, taskStatus: tasks2.status }).from(runs2).innerJoin(tasks2, eq2(tasks2.id, runs2.taskId)).where(eq2(runs2.id, runId));
|
|
1200
1231
|
return row ? { runStatus: row.runStatus, taskStatus: row.taskStatus ?? "" } : null;
|
|
@@ -1275,8 +1306,8 @@ var SqliteStore = class {
|
|
|
1275
1306
|
);
|
|
1276
1307
|
return logs.length;
|
|
1277
1308
|
}
|
|
1278
|
-
async getRunLogs(runId) {
|
|
1279
|
-
const rows = await this.db.select().from(runLogs2).where(eq2(runLogs2.runId, runId)).orderBy(asc2(runLogs2.timestamp));
|
|
1309
|
+
async getRunLogs(runId, afterId) {
|
|
1310
|
+
const rows = await this.db.select().from(runLogs2).where(afterId === void 0 ? eq2(runLogs2.runId, runId) : and2(eq2(runLogs2.runId, runId), gt2(runLogs2.id, afterId))).orderBy(asc2(runLogs2.timestamp), asc2(runLogs2.id));
|
|
1280
1311
|
return rows.map(toLogRecord2);
|
|
1281
1312
|
}
|
|
1282
1313
|
async createArtifact(runId, input) {
|
|
@@ -1468,7 +1499,7 @@ var SqliteStore = class {
|
|
|
1468
1499
|
}
|
|
1469
1500
|
};
|
|
1470
1501
|
|
|
1471
|
-
//
|
|
1502
|
+
// ../../libs/db/src/client.ts
|
|
1472
1503
|
var pools = /* @__PURE__ */ new Map();
|
|
1473
1504
|
function getPool(databaseUrl) {
|
|
1474
1505
|
const existing = pools.get(databaseUrl);
|
|
@@ -1656,7 +1687,7 @@ function validateUpdateSchedule(body) {
|
|
|
1656
1687
|
return updateScheduleSchema.parse(body);
|
|
1657
1688
|
}
|
|
1658
1689
|
|
|
1659
|
-
//
|
|
1690
|
+
// ../../libs/agent-skills/src/skills/code-review.ts
|
|
1660
1691
|
function codeReviewPlan(opts = {}) {
|
|
1661
1692
|
const { repoPath = ".", model = "claude-haiku-4-5-20251001", branch } = opts;
|
|
1662
1693
|
const diffCmd = branch ? `git diff ${branch}..HEAD` : "git diff HEAD~1";
|
|
@@ -1693,7 +1724,7 @@ function codeReviewPlan(opts = {}) {
|
|
|
1693
1724
|
};
|
|
1694
1725
|
}
|
|
1695
1726
|
|
|
1696
|
-
//
|
|
1727
|
+
// ../../libs/agent-skills/src/skills/hello.ts
|
|
1697
1728
|
function helloPlan(opts = {}) {
|
|
1698
1729
|
const message = opts.message ?? "hello from workplane";
|
|
1699
1730
|
return {
|
|
@@ -1712,7 +1743,7 @@ function helloPlan(opts = {}) {
|
|
|
1712
1743
|
};
|
|
1713
1744
|
}
|
|
1714
1745
|
|
|
1715
|
-
//
|
|
1746
|
+
// ../../libs/agent-skills/src/skills/summarize-file.ts
|
|
1716
1747
|
function summarizeFilePlan(opts) {
|
|
1717
1748
|
const { filePath, provider = "ollama", model = "llama3" } = opts;
|
|
1718
1749
|
return {
|
|
@@ -1739,7 +1770,7 @@ function summarizeFilePlan(opts) {
|
|
|
1739
1770
|
};
|
|
1740
1771
|
}
|
|
1741
1772
|
|
|
1742
|
-
//
|
|
1773
|
+
// ../../libs/agent-skills/src/skills/index.ts
|
|
1743
1774
|
var SkillRegistry = class {
|
|
1744
1775
|
skills = /* @__PURE__ */ new Map();
|
|
1745
1776
|
register(entry) {
|
|
@@ -1757,6 +1788,12 @@ function createDefaultRegistry() {
|
|
|
1757
1788
|
registry.register({
|
|
1758
1789
|
name: "hello",
|
|
1759
1790
|
description: "Shell echo (scheduler smoke test)",
|
|
1791
|
+
inputSchema: {
|
|
1792
|
+
type: "object",
|
|
1793
|
+
properties: {
|
|
1794
|
+
message: { type: "string", description: "Text to echo", default: "hello from workplane" }
|
|
1795
|
+
}
|
|
1796
|
+
},
|
|
1760
1797
|
buildPlan: (opts) => helloPlan({
|
|
1761
1798
|
message: typeof opts.message === "string" ? opts.message : void 0
|
|
1762
1799
|
})
|
|
@@ -1764,6 +1801,14 @@ function createDefaultRegistry() {
|
|
|
1764
1801
|
registry.register({
|
|
1765
1802
|
name: "code-review",
|
|
1766
1803
|
description: "Git diff \u2192 local summarize (ollama) \u2192 frontier critique (anthropic)",
|
|
1804
|
+
inputSchema: {
|
|
1805
|
+
type: "object",
|
|
1806
|
+
properties: {
|
|
1807
|
+
repo: { type: "string", description: "Path to the git repository" },
|
|
1808
|
+
model: { type: "string", description: "Model used for the local summarize step" },
|
|
1809
|
+
branch: { type: "string", description: "Branch to diff against" }
|
|
1810
|
+
}
|
|
1811
|
+
},
|
|
1767
1812
|
buildPlan: (opts) => codeReviewPlan({
|
|
1768
1813
|
repoPath: typeof opts.repo === "string" ? opts.repo : void 0,
|
|
1769
1814
|
model: typeof opts.model === "string" ? opts.model : void 0,
|
|
@@ -1773,6 +1818,15 @@ function createDefaultRegistry() {
|
|
|
1773
1818
|
registry.register({
|
|
1774
1819
|
name: "summarize-file",
|
|
1775
1820
|
description: "Read file \u2192 summarize with configurable provider (default: ollama)",
|
|
1821
|
+
inputSchema: {
|
|
1822
|
+
type: "object",
|
|
1823
|
+
properties: {
|
|
1824
|
+
file: { type: "string", description: "Path of the file to summarize" },
|
|
1825
|
+
provider: { type: "string", description: "Model provider", default: "ollama" },
|
|
1826
|
+
model: { type: "string", description: "Model name" }
|
|
1827
|
+
},
|
|
1828
|
+
required: ["file"]
|
|
1829
|
+
},
|
|
1776
1830
|
buildPlan: (opts) => {
|
|
1777
1831
|
const filePath = typeof opts.file === "string" ? opts.file : "";
|
|
1778
1832
|
if (!filePath) throw new Error("--file is required for summarize-file");
|
|
@@ -1786,77 +1840,7 @@ function createDefaultRegistry() {
|
|
|
1786
1840
|
return registry;
|
|
1787
1841
|
}
|
|
1788
1842
|
|
|
1789
|
-
//
|
|
1790
|
-
function printStepResult(stepName, output) {
|
|
1791
|
-
process.stdout.write(`=== step: ${stepName} ===
|
|
1792
|
-
${output}
|
|
1793
|
-
|
|
1794
|
-
`);
|
|
1795
|
-
}
|
|
1796
|
-
|
|
1797
|
-
// ../workplans/src/context.ts
|
|
1798
|
-
var WorkplaneNodeHandle = class {
|
|
1799
|
-
constructor(serverUrl, nodeToken) {
|
|
1800
|
-
this.serverUrl = serverUrl;
|
|
1801
|
-
this.nodeToken = nodeToken;
|
|
1802
|
-
}
|
|
1803
|
-
serverUrl;
|
|
1804
|
-
nodeToken;
|
|
1805
|
-
async execute(step) {
|
|
1806
|
-
const start = Date.now();
|
|
1807
|
-
const taskRes = await fetch(`${this.serverUrl}/tasks`, {
|
|
1808
|
-
method: "POST",
|
|
1809
|
-
headers: {
|
|
1810
|
-
"content-type": "application/json",
|
|
1811
|
-
authorization: `Bearer ${this.nodeToken}`
|
|
1812
|
-
},
|
|
1813
|
-
body: JSON.stringify({
|
|
1814
|
-
kind: "workplan.step",
|
|
1815
|
-
adapter: step.adapter,
|
|
1816
|
-
payload: step.payload,
|
|
1817
|
-
requires: step.requires ?? []
|
|
1818
|
-
})
|
|
1819
|
-
});
|
|
1820
|
-
if (!taskRes.ok) {
|
|
1821
|
-
throw new Error(`failed to submit step ${step.id}: ${taskRes.status}`);
|
|
1822
|
-
}
|
|
1823
|
-
const task = await taskRes.json();
|
|
1824
|
-
const output = await this.pollUntilDone(task.id);
|
|
1825
|
-
return { stepId: step.id, output, exitCode: 0, durationMs: Date.now() - start };
|
|
1826
|
-
}
|
|
1827
|
-
async pollUntilDone(taskId) {
|
|
1828
|
-
const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
|
|
1829
|
-
for (; ; ) {
|
|
1830
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
1831
|
-
const res = await fetch(`${this.serverUrl}/tasks/${taskId}`, {
|
|
1832
|
-
headers: { authorization: `Bearer ${this.nodeToken}` }
|
|
1833
|
-
});
|
|
1834
|
-
const task = await res.json();
|
|
1835
|
-
if (terminalStatuses.has(task.status)) {
|
|
1836
|
-
if (task.status !== "succeeded") {
|
|
1837
|
-
throw new Error(`step task ${taskId} ended with status: ${task.status}`);
|
|
1838
|
-
}
|
|
1839
|
-
return typeof task.payload?.output === "string" ? task.payload.output : "";
|
|
1840
|
-
}
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
};
|
|
1844
|
-
var LocalWorkplanContext = class {
|
|
1845
|
-
serverUrl;
|
|
1846
|
-
nodeToken;
|
|
1847
|
-
constructor(opts = {}) {
|
|
1848
|
-
this.serverUrl = opts.serverUrl ?? process.env.WORKPLANE_SERVER_URL ?? "http://localhost:8787";
|
|
1849
|
-
this.nodeToken = opts.nodeToken ?? process.env.WORKPLANE_NODE_TOKEN ?? "";
|
|
1850
|
-
}
|
|
1851
|
-
async resolveNode(_requires, _provider) {
|
|
1852
|
-
return new WorkplaneNodeHandle(this.serverUrl, this.nodeToken);
|
|
1853
|
-
}
|
|
1854
|
-
emitResult(_stepId, stepName, output) {
|
|
1855
|
-
printStepResult(stepName, output);
|
|
1856
|
-
}
|
|
1857
|
-
};
|
|
1858
|
-
|
|
1859
|
-
// ../workplans/src/template.ts
|
|
1843
|
+
// ../../libs/workplans/src/template.ts
|
|
1860
1844
|
function applyTemplate(payload, prevOutput) {
|
|
1861
1845
|
return Object.fromEntries(
|
|
1862
1846
|
Object.entries(payload).map(([k, v]) => [
|
|
@@ -1866,7 +1850,7 @@ function applyTemplate(payload, prevOutput) {
|
|
|
1866
1850
|
);
|
|
1867
1851
|
}
|
|
1868
1852
|
|
|
1869
|
-
//
|
|
1853
|
+
// ../../libs/workplans/src/runner.ts
|
|
1870
1854
|
var INLINE_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai", "ollama", "shell", "file"]);
|
|
1871
1855
|
async function runInlineStep(step, prompt) {
|
|
1872
1856
|
const provider = step.provider;
|
|
@@ -1962,7 +1946,77 @@ var SequentialWorkplanRunner = class {
|
|
|
1962
1946
|
}
|
|
1963
1947
|
};
|
|
1964
1948
|
|
|
1965
|
-
//
|
|
1949
|
+
// ../../libs/workplans/src/sinks/stdout.ts
|
|
1950
|
+
function printStepResult(stepName, output) {
|
|
1951
|
+
process.stdout.write(`=== step: ${stepName} ===
|
|
1952
|
+
${output}
|
|
1953
|
+
|
|
1954
|
+
`);
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// ../../libs/workplans/src/context.ts
|
|
1958
|
+
var WorkplaneNodeHandle = class {
|
|
1959
|
+
constructor(serverUrl, nodeToken) {
|
|
1960
|
+
this.serverUrl = serverUrl;
|
|
1961
|
+
this.nodeToken = nodeToken;
|
|
1962
|
+
}
|
|
1963
|
+
serverUrl;
|
|
1964
|
+
nodeToken;
|
|
1965
|
+
async execute(step) {
|
|
1966
|
+
const start = Date.now();
|
|
1967
|
+
const taskRes = await fetch(`${this.serverUrl}/tasks`, {
|
|
1968
|
+
method: "POST",
|
|
1969
|
+
headers: {
|
|
1970
|
+
"content-type": "application/json",
|
|
1971
|
+
authorization: `Bearer ${this.nodeToken}`
|
|
1972
|
+
},
|
|
1973
|
+
body: JSON.stringify({
|
|
1974
|
+
kind: "workplan.step",
|
|
1975
|
+
adapter: step.adapter,
|
|
1976
|
+
payload: step.payload,
|
|
1977
|
+
requires: step.requires ?? []
|
|
1978
|
+
})
|
|
1979
|
+
});
|
|
1980
|
+
if (!taskRes.ok) {
|
|
1981
|
+
throw new Error(`failed to submit step ${step.id}: ${taskRes.status}`);
|
|
1982
|
+
}
|
|
1983
|
+
const task = await taskRes.json();
|
|
1984
|
+
const output = await this.pollUntilDone(task.id);
|
|
1985
|
+
return { stepId: step.id, output, exitCode: 0, durationMs: Date.now() - start };
|
|
1986
|
+
}
|
|
1987
|
+
async pollUntilDone(taskId) {
|
|
1988
|
+
const terminalStatuses = /* @__PURE__ */ new Set(["succeeded", "failed", "cancelled"]);
|
|
1989
|
+
for (; ; ) {
|
|
1990
|
+
await new Promise((r) => setTimeout(r, 1e3));
|
|
1991
|
+
const res = await fetch(`${this.serverUrl}/tasks/${taskId}`, {
|
|
1992
|
+
headers: { authorization: `Bearer ${this.nodeToken}` }
|
|
1993
|
+
});
|
|
1994
|
+
const task = await res.json();
|
|
1995
|
+
if (terminalStatuses.has(task.status)) {
|
|
1996
|
+
if (task.status !== "succeeded") {
|
|
1997
|
+
throw new Error(`step task ${taskId} ended with status: ${task.status}`);
|
|
1998
|
+
}
|
|
1999
|
+
return typeof task.payload?.output === "string" ? task.payload.output : "";
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
};
|
|
2004
|
+
var LocalWorkplanContext = class {
|
|
2005
|
+
serverUrl;
|
|
2006
|
+
nodeToken;
|
|
2007
|
+
constructor(opts = {}) {
|
|
2008
|
+
this.serverUrl = opts.serverUrl ?? process.env.WORKPLANE_SERVER_URL ?? "http://localhost:8787";
|
|
2009
|
+
this.nodeToken = opts.nodeToken ?? process.env.WORKPLANE_NODE_TOKEN ?? "";
|
|
2010
|
+
}
|
|
2011
|
+
async resolveNode(_requires, _provider) {
|
|
2012
|
+
return new WorkplaneNodeHandle(this.serverUrl, this.nodeToken);
|
|
2013
|
+
}
|
|
2014
|
+
emitResult(_stepId, stepName, output) {
|
|
2015
|
+
printStepResult(stepName, output);
|
|
2016
|
+
}
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
// ../../libs/workplans/src/schedule-builder.ts
|
|
1966
2020
|
import { CronExpressionParser } from "cron-parser";
|
|
1967
2021
|
var ScheduleBuilder = class _ScheduleBuilder {
|
|
1968
2022
|
static nextRunAt(cronExpression, timezone) {
|
|
@@ -1977,7 +2031,7 @@ var ScheduleBuilder = class _ScheduleBuilder {
|
|
|
1977
2031
|
}
|
|
1978
2032
|
};
|
|
1979
2033
|
|
|
1980
|
-
//
|
|
2034
|
+
// ../../libs/workplans/src/scheduler.ts
|
|
1981
2035
|
var WorkplanScheduler = class {
|
|
1982
2036
|
constructor(store, resolver, contextFactory, runner = new SequentialWorkplanRunner()) {
|
|
1983
2037
|
this.store = store;
|
|
@@ -2055,6 +2109,13 @@ var WorkplanScheduler = class {
|
|
|
2055
2109
|
|
|
2056
2110
|
// ../server/src/schedule-service.ts
|
|
2057
2111
|
var skillRegistry = createDefaultRegistry();
|
|
2112
|
+
function listSkillSummaries() {
|
|
2113
|
+
return skillRegistry.list().map((skill) => ({
|
|
2114
|
+
name: skill.name,
|
|
2115
|
+
description: skill.description,
|
|
2116
|
+
inputSchema: skill.inputSchema ?? { type: "object", properties: {} }
|
|
2117
|
+
}));
|
|
2118
|
+
}
|
|
2058
2119
|
function createWorkplanScheduler(store) {
|
|
2059
2120
|
return new WorkplanScheduler(
|
|
2060
2121
|
store,
|
|
@@ -2123,29 +2184,6 @@ function listKnownPlanIds() {
|
|
|
2123
2184
|
return skillRegistry.list().map((skill) => skill.name);
|
|
2124
2185
|
}
|
|
2125
2186
|
|
|
2126
|
-
// ../core/src/auth.ts
|
|
2127
|
-
function parseBearerToken(authorizationHeader) {
|
|
2128
|
-
if (!authorizationHeader) {
|
|
2129
|
-
return void 0;
|
|
2130
|
-
}
|
|
2131
|
-
const match = /^Bearer\s+(.+)$/i.exec(authorizationHeader.trim());
|
|
2132
|
-
return match?.[1];
|
|
2133
|
-
}
|
|
2134
|
-
function isAuthorizedBearer(provided, expected) {
|
|
2135
|
-
if (!expected) {
|
|
2136
|
-
return true;
|
|
2137
|
-
}
|
|
2138
|
-
if (!provided) {
|
|
2139
|
-
return false;
|
|
2140
|
-
}
|
|
2141
|
-
return provided === expected;
|
|
2142
|
-
}
|
|
2143
|
-
function getRequestBearer(req) {
|
|
2144
|
-
const header = req.headers.authorization;
|
|
2145
|
-
const value = Array.isArray(header) ? header[0] : header;
|
|
2146
|
-
return parseBearerToken(value);
|
|
2147
|
-
}
|
|
2148
|
-
|
|
2149
2187
|
// ../server/src/auth-middleware.ts
|
|
2150
2188
|
function checkRouteAuth(req, res, auth, config) {
|
|
2151
2189
|
const bearer = getRequestBearer(req);
|
|
@@ -2161,6 +2199,16 @@ function checkRouteAuth(req, res, auth, config) {
|
|
|
2161
2199
|
}
|
|
2162
2200
|
return true;
|
|
2163
2201
|
}
|
|
2202
|
+
if (auth === "read") {
|
|
2203
|
+
const accepted = [config.operatorToken, config.nodeToken].some((token) => token && bearer === token);
|
|
2204
|
+
if (!accepted) {
|
|
2205
|
+
res.statusCode = 401;
|
|
2206
|
+
res.setHeader("content-type", "application/json");
|
|
2207
|
+
res.end(JSON.stringify({ error: "unauthorized read request" }));
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
return true;
|
|
2211
|
+
}
|
|
2164
2212
|
if (auth === "operator") {
|
|
2165
2213
|
if (!isAuthorizedBearer(bearer, config.operatorToken)) {
|
|
2166
2214
|
res.statusCode = 401;
|
|
@@ -2172,7 +2220,14 @@ function checkRouteAuth(req, res, auth, config) {
|
|
|
2172
2220
|
}
|
|
2173
2221
|
return true;
|
|
2174
2222
|
}
|
|
2175
|
-
function routeAuthFor(method, pathname) {
|
|
2223
|
+
function routeAuthFor(method, pathname, options = {}) {
|
|
2224
|
+
const auth = baseRouteAuthFor(method, pathname);
|
|
2225
|
+
if (options.protectReads && auth === "public" && method === "GET" && pathname !== "/healthz") {
|
|
2226
|
+
return "read";
|
|
2227
|
+
}
|
|
2228
|
+
return auth;
|
|
2229
|
+
}
|
|
2230
|
+
function baseRouteAuthFor(method, pathname) {
|
|
2176
2231
|
if (pathname === "/healthz") {
|
|
2177
2232
|
return "public";
|
|
2178
2233
|
}
|
|
@@ -2192,7 +2247,7 @@ function routeAuthFor(method, pathname) {
|
|
|
2192
2247
|
return "node";
|
|
2193
2248
|
}
|
|
2194
2249
|
if (method === "GET" && /^\/runs\/[^/]+\/input$/.test(pathname)) {
|
|
2195
|
-
return "
|
|
2250
|
+
return "read";
|
|
2196
2251
|
}
|
|
2197
2252
|
if (method === "POST" && /^\/runs\/[^/]+\/input\/\d+\/delivered$/.test(pathname)) {
|
|
2198
2253
|
return "node";
|
|
@@ -2233,6 +2288,40 @@ function requiresConfiguredToken(auth, config) {
|
|
|
2233
2288
|
if (auth === "operator") {
|
|
2234
2289
|
return Boolean(config.operatorToken);
|
|
2235
2290
|
}
|
|
2291
|
+
if (auth === "read") {
|
|
2292
|
+
return Boolean(config.operatorToken || config.nodeToken);
|
|
2293
|
+
}
|
|
2294
|
+
return false;
|
|
2295
|
+
}
|
|
2296
|
+
|
|
2297
|
+
// ../server/src/cors.ts
|
|
2298
|
+
function parseCorsOrigins(value) {
|
|
2299
|
+
const entries = (value ?? "").split(",").map((entry) => entry.trim()).filter(Boolean);
|
|
2300
|
+
if (entries.length === 0) {
|
|
2301
|
+
return null;
|
|
2302
|
+
}
|
|
2303
|
+
return { allowAll: entries.includes("*"), origins: new Set(entries) };
|
|
2304
|
+
}
|
|
2305
|
+
function applyCors(req, res, config) {
|
|
2306
|
+
if (!config) {
|
|
2307
|
+
return false;
|
|
2308
|
+
}
|
|
2309
|
+
const origin = req.headers.origin;
|
|
2310
|
+
const allowed = typeof origin === "string" && (config.allowAll || config.origins.has(origin));
|
|
2311
|
+
if (allowed) {
|
|
2312
|
+
res.setHeader("access-control-allow-origin", config.allowAll ? "*" : origin);
|
|
2313
|
+
res.setHeader("vary", "Origin");
|
|
2314
|
+
}
|
|
2315
|
+
if (req.method === "OPTIONS" && typeof req.headers["access-control-request-method"] === "string") {
|
|
2316
|
+
if (allowed) {
|
|
2317
|
+
res.setHeader("access-control-allow-methods", "GET,POST,PATCH,DELETE,OPTIONS");
|
|
2318
|
+
res.setHeader("access-control-allow-headers", "authorization,content-type");
|
|
2319
|
+
res.setHeader("access-control-max-age", "600");
|
|
2320
|
+
}
|
|
2321
|
+
res.statusCode = allowed ? 204 : 403;
|
|
2322
|
+
res.end();
|
|
2323
|
+
return true;
|
|
2324
|
+
}
|
|
2236
2325
|
return false;
|
|
2237
2326
|
}
|
|
2238
2327
|
|
|
@@ -2290,14 +2379,24 @@ async function main() {
|
|
|
2290
2379
|
if (process.env.WORKPLANE_USE_DBOS !== "true" && isSchedulerEnabled()) {
|
|
2291
2380
|
stopSchedulerLoop = startSchedulerLoop(scheduler);
|
|
2292
2381
|
}
|
|
2382
|
+
const corsConfig = parseCorsOrigins(process.env.WORKPLANE_CORS_ORIGINS);
|
|
2383
|
+
const protectReads = process.env.WORKPLANE_PROTECT_READS === "true";
|
|
2384
|
+
if (protectReads && !(config.operatorToken || config.nodeToken)) {
|
|
2385
|
+
process.stderr.write("WORKPLANE_PROTECT_READS=true has no effect: set WORKPLANE_OPERATOR_TOKEN (and WORKPLANE_NODE_TOKEN) first\n");
|
|
2386
|
+
} else if (protectReads && !config.nodeToken) {
|
|
2387
|
+
process.stderr.write("WORKPLANE_PROTECT_READS=true: also set WORKPLANE_NODE_TOKEN, nodes read /runs/:id and /tasks/:id without a token otherwise\n");
|
|
2388
|
+
}
|
|
2293
2389
|
const server = createServer(async (req, res) => {
|
|
2294
2390
|
try {
|
|
2295
2391
|
if (!req.url || !req.method) {
|
|
2296
2392
|
writeJson(res, 400, { error: "invalid request" });
|
|
2297
2393
|
return;
|
|
2298
2394
|
}
|
|
2395
|
+
if (applyCors(req, res, corsConfig)) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2299
2398
|
const url = new URL(req.url, "http://localhost");
|
|
2300
|
-
const routeAuth = routeAuthFor(req.method, url.pathname);
|
|
2399
|
+
const routeAuth = routeAuthFor(req.method, url.pathname, { protectReads });
|
|
2301
2400
|
const authConfig = { nodeToken: config.nodeToken, operatorToken: config.operatorToken };
|
|
2302
2401
|
if (requiresConfiguredToken(routeAuth, authConfig) && !checkRouteAuth(req, res, routeAuth, authConfig)) {
|
|
2303
2402
|
return;
|
|
@@ -2373,7 +2472,13 @@ async function main() {
|
|
|
2373
2472
|
}
|
|
2374
2473
|
if (req.method === "GET" && /^\/runs\/[^/]+\/logs$/.test(url.pathname)) {
|
|
2375
2474
|
const runId = url.pathname.split("/")[2];
|
|
2376
|
-
const
|
|
2475
|
+
const afterIdParam = url.searchParams.get("afterId");
|
|
2476
|
+
const afterId = afterIdParam === null ? void 0 : Number(afterIdParam);
|
|
2477
|
+
if (afterId !== void 0 && (!Number.isInteger(afterId) || afterId < 0)) {
|
|
2478
|
+
writeJson(res, 400, { error: "afterId must be a non-negative integer" });
|
|
2479
|
+
return;
|
|
2480
|
+
}
|
|
2481
|
+
const logs = await store.getRunLogs(runId, afterId);
|
|
2377
2482
|
writeJson(res, 200, { logs });
|
|
2378
2483
|
return;
|
|
2379
2484
|
}
|
|
@@ -2383,6 +2488,11 @@ async function main() {
|
|
|
2383
2488
|
writeJson(res, 200, { artifacts: artifacts3 });
|
|
2384
2489
|
return;
|
|
2385
2490
|
}
|
|
2491
|
+
if (req.method === "GET" && url.pathname === "/nodes") {
|
|
2492
|
+
const nodes3 = await store.listNodes();
|
|
2493
|
+
writeJson(res, 200, { nodes: nodes3 });
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2386
2496
|
if (req.method === "POST" && url.pathname === "/nodes/register") {
|
|
2387
2497
|
const body = await readJson(req);
|
|
2388
2498
|
const node = await store.registerNode(body.name, body.capabilities ?? [], body.nodeId);
|
|
@@ -2453,6 +2563,10 @@ async function main() {
|
|
|
2453
2563
|
writeJson(res, 200, { ok: true });
|
|
2454
2564
|
return;
|
|
2455
2565
|
}
|
|
2566
|
+
if (req.method === "GET" && url.pathname === "/skills") {
|
|
2567
|
+
writeJson(res, 200, { skills: listSkillSummaries() });
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2456
2570
|
if (req.method === "GET" && url.pathname === "/plans") {
|
|
2457
2571
|
writeJson(res, 200, { plans: listKnownPlanIds() });
|
|
2458
2572
|
return;
|