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/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
- // ../workplans/src/providers/anthropic.ts
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
- "../workplans/src/providers/anthropic.ts"() {
29
+ "../../libs/workplans/src/providers/anthropic.ts"() {
30
30
  "use strict";
31
31
  }
32
32
  });
33
33
 
34
- // ../workplans/src/providers/openai.ts
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
- "../workplans/src/providers/openai.ts"() {
49
+ "../../libs/workplans/src/providers/openai.ts"() {
50
50
  "use strict";
51
51
  }
52
52
  });
53
53
 
54
- // ../workplans/src/providers/ollama.ts
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
- "../workplans/src/providers/ollama.ts"() {
67
+ "../../libs/workplans/src/providers/ollama.ts"() {
68
68
  "use strict";
69
69
  }
70
70
  });
71
71
 
72
- // ../workplans/src/providers/shell.ts
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
- "../workplans/src/providers/shell.ts"() {
88
+ "../../libs/workplans/src/providers/shell.ts"() {
89
89
  "use strict";
90
90
  execAsync = promisify(exec);
91
91
  }
92
92
  });
93
93
 
94
- // ../workplans/src/providers/file.ts
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
- "../workplans/src/providers/file.ts"() {
104
+ "../../libs/workplans/src/providers/file.ts"() {
105
105
  "use strict";
106
106
  }
107
107
  });
108
108
 
109
- // ../dbos/src/scheduler.ts
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
- "../dbos/src/scheduler.ts"() {
129
+ "../../libs/dbos/src/scheduler.ts"() {
130
130
  "use strict";
131
131
  registered = false;
132
132
  }
133
133
  });
134
134
 
135
- // ../dbos/src/index.ts
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
- "../dbos/src/index.ts"() {
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
- // ../core/src/config.ts
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
- // ../core/src/env.ts
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
- // ../core/src/config.ts
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
- // ../db/src/client.ts
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
- // ../db/src/pg-store.ts
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
- // ../core/src/ids.ts
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
- // ../db/src/pg-store.ts
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
- // ../db/src/sqlite-store.ts
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
- // ../db/src/schema/sqlite.ts
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
- // ../db/src/sqlite-store.ts
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
- // ../db/src/client.ts
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
- // ../agent-skills/src/skills/code-review.ts
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
- // ../agent-skills/src/skills/hello.ts
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
- // ../agent-skills/src/skills/summarize-file.ts
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
- // ../agent-skills/src/skills/index.ts
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
- // ../workplans/src/sinks/stdout.ts
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
- // ../workplans/src/runner.ts
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
- // ../workplans/src/schedule-builder.ts
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
- // ../workplans/src/scheduler.ts
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 "node";
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 logs = await store.getRunLogs(runId);
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;