@hasna/workflows 0.1.2 → 0.1.3
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/agent-tools.js +3 -3
- package/dist/cli/index.js +55 -31
- package/dist/index.js +46 -22
- package/dist/mcp/index.js +35 -11
- package/dist/serve/index.js +46 -22
- package/dist/service.js +46 -22
- package/hasna.contract.json +1 -1
- package/package.json +4 -3
- package/types/paths.d.ts +51 -0
- package/types/types.d.ts +3 -1
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ console.log(svc.health());
|
|
|
102
102
|
|---|---|---|
|
|
103
103
|
| `HASNA_WORKFLOWS_PORT` / `WORKFLOWS_PORT` | serve port | 8790 |
|
|
104
104
|
| `HASNA_WORKFLOWS_HOST` / `WORKFLOWS_HOST` | serve bind host | 127.0.0.1 |
|
|
105
|
-
| `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR` | data directory | `~/.hasna/workflows` |
|
|
105
|
+
| `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR` | data directory | `~/.hasna/workflows` (the @hasna/paths XDG data home `~/.local/share/hasna/workflows` is adopted once `HASNA_DATA_HOME` is set or the store is migrated there) |
|
|
106
106
|
|
|
107
107
|
The server data backend is sqlite (default) or postgresql when
|
|
108
108
|
`HASNA_WORKFLOWS_DATABASE_URL` is set.
|
package/dist/agent-tools.js
CHANGED
|
@@ -1411,9 +1411,9 @@ class WorkflowsDaemon {
|
|
|
1411
1411
|
}
|
|
1412
1412
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1413
1413
|
if (!result.ok) {
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1414
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1415
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1416
|
+
if (shouldRetry) {
|
|
1417
1417
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1418
1418
|
return { kind: "advanced", nodeId };
|
|
1419
1419
|
}
|
package/dist/cli/index.js
CHANGED
|
@@ -443,16 +443,13 @@ var init_lanes = __esm(() => {
|
|
|
443
443
|
|
|
444
444
|
// src/cli/index.ts
|
|
445
445
|
import { Command } from "commander";
|
|
446
|
-
import { existsSync as
|
|
446
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, rmSync } from "fs";
|
|
447
447
|
import { hostname } from "os";
|
|
448
448
|
import { join as join4 } from "path";
|
|
449
|
-
|
|
450
|
-
// src/service.ts
|
|
451
|
-
import { join as join3 } from "path";
|
|
452
449
|
// package.json
|
|
453
450
|
var package_default = {
|
|
454
451
|
name: "@hasna/workflows",
|
|
455
|
-
version: "0.1.
|
|
452
|
+
version: "0.1.3",
|
|
456
453
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
457
454
|
type: "module",
|
|
458
455
|
main: "./dist/index.js",
|
|
@@ -522,9 +519,10 @@ var package_default = {
|
|
|
522
519
|
author: "Hasna",
|
|
523
520
|
license: "Apache-2.0",
|
|
524
521
|
dependencies: {
|
|
525
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
522
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
526
523
|
"@cursor/sdk": "^1.0.28",
|
|
527
|
-
"@
|
|
524
|
+
"@hasna/paths": "0.1.0",
|
|
525
|
+
"@openai/codex-sdk": "0.147.0",
|
|
528
526
|
commander: "^13.1.0"
|
|
529
527
|
},
|
|
530
528
|
devDependencies: {
|
|
@@ -1535,9 +1533,9 @@ class WorkflowsDaemon {
|
|
|
1535
1533
|
}
|
|
1536
1534
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1537
1535
|
if (!result.ok) {
|
|
1538
|
-
const
|
|
1539
|
-
|
|
1540
|
-
|
|
1536
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1537
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1538
|
+
if (shouldRetry) {
|
|
1541
1539
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1542
1540
|
return { kind: "advanced", nodeId };
|
|
1543
1541
|
}
|
|
@@ -1669,17 +1667,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1669
1667
|
return store.getRun(run.id);
|
|
1670
1668
|
}
|
|
1671
1669
|
|
|
1670
|
+
// src/paths.ts
|
|
1671
|
+
import { existsSync } from "fs";
|
|
1672
|
+
import { homedir } from "os";
|
|
1673
|
+
import { join } from "path";
|
|
1674
|
+
import { dataDir } from "@hasna/paths";
|
|
1675
|
+
var APP = "workflows";
|
|
1676
|
+
var STORE_DB_FILE = "workflows.db";
|
|
1677
|
+
function getHomeDir(env = process.env) {
|
|
1678
|
+
return env["HOME"] || env["USERPROFILE"] || homedir();
|
|
1679
|
+
}
|
|
1680
|
+
function getLegacyDataDir(env = process.env) {
|
|
1681
|
+
return join(getHomeDir(env), ".hasna", APP);
|
|
1682
|
+
}
|
|
1683
|
+
function getResolverDataDir(env = process.env) {
|
|
1684
|
+
return dataDir({ app: APP, home: getHomeDir(env), env });
|
|
1685
|
+
}
|
|
1686
|
+
function adoptResolverDataDir(resolved, env = process.env) {
|
|
1687
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
1688
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
1689
|
+
return true;
|
|
1690
|
+
return existsSync(join(resolved, STORE_DB_FILE));
|
|
1691
|
+
}
|
|
1692
|
+
function getEffectiveDataDir(env = process.env) {
|
|
1693
|
+
const resolved = getResolverDataDir(env);
|
|
1694
|
+
return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1672
1697
|
// src/store.ts
|
|
1673
1698
|
import { Database } from "bun:sqlite";
|
|
1674
1699
|
import { mkdirSync } from "fs";
|
|
1675
|
-
import { join } from "path";
|
|
1700
|
+
import { join as join2 } from "path";
|
|
1676
1701
|
import { randomUUID } from "crypto";
|
|
1677
1702
|
function nowIso2() {
|
|
1678
1703
|
return new Date().toISOString();
|
|
1679
1704
|
}
|
|
1680
|
-
function openStore(
|
|
1681
|
-
mkdirSync(
|
|
1682
|
-
const db = new Database(
|
|
1705
|
+
function openStore(dataDir2) {
|
|
1706
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1707
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1683
1708
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1684
1709
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1685
1710
|
db.exec(`
|
|
@@ -1857,8 +1882,8 @@ function openStore(dataDir) {
|
|
|
1857
1882
|
|
|
1858
1883
|
// src/wal.ts
|
|
1859
1884
|
import { createHash as createHash3 } from "crypto";
|
|
1860
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1861
|
-
import { join as
|
|
1885
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1886
|
+
import { join as join3 } from "path";
|
|
1862
1887
|
var WAL_FILE_NAME = "session.wal";
|
|
1863
1888
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1864
1889
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1873,11 +1898,11 @@ class SessionWAL {
|
|
|
1873
1898
|
constructor(filePath) {
|
|
1874
1899
|
this.filePath = filePath;
|
|
1875
1900
|
}
|
|
1876
|
-
static open(
|
|
1877
|
-
mkdirSync2(
|
|
1878
|
-
const canonical =
|
|
1879
|
-
const legacy =
|
|
1880
|
-
if (
|
|
1901
|
+
static open(dataDir2) {
|
|
1902
|
+
mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
|
|
1903
|
+
const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
|
|
1904
|
+
const legacy = join3(dataDir2, WAL_FILE_NAME);
|
|
1905
|
+
if (existsSync2(legacy) && !existsSync2(canonical)) {
|
|
1881
1906
|
try {
|
|
1882
1907
|
renameSync(legacy, canonical);
|
|
1883
1908
|
} catch {
|
|
@@ -2008,11 +2033,10 @@ function envInt(names, fallback) {
|
|
|
2008
2033
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2009
2034
|
}
|
|
2010
2035
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
2011
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
2012
2036
|
return {
|
|
2013
2037
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
2014
2038
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
2015
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2039
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
2016
2040
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
2017
2041
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
2018
2042
|
};
|
|
@@ -2403,7 +2427,7 @@ program.command("init").description("Create the store layout (workflows/, sessio
|
|
|
2403
2427
|
const store = openStore(config.dataDir);
|
|
2404
2428
|
store.close();
|
|
2405
2429
|
const target = file ?? join4(config.dataDir, "workflows", "demo.json");
|
|
2406
|
-
if (
|
|
2430
|
+
if (existsSync3(target)) {
|
|
2407
2431
|
console.error(`refusing to overwrite existing file ${target}`);
|
|
2408
2432
|
process.exit(1);
|
|
2409
2433
|
}
|
|
@@ -2690,16 +2714,16 @@ program.command("machines").description("List observed machines or report this m
|
|
|
2690
2714
|
case "status": {
|
|
2691
2715
|
const replay = wal.replay();
|
|
2692
2716
|
const pidFile = join4(config.dataDir, "daemon.pid");
|
|
2693
|
-
const pid =
|
|
2717
|
+
const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
|
|
2694
2718
|
const running = pid !== null && processAlive(Number(pid));
|
|
2695
2719
|
const report = {
|
|
2696
2720
|
hostname: hostname(),
|
|
2697
2721
|
pid: process.pid,
|
|
2698
2722
|
dataDir: config.dataDir,
|
|
2699
2723
|
layout: {
|
|
2700
|
-
workflows:
|
|
2701
|
-
sessions:
|
|
2702
|
-
db:
|
|
2724
|
+
workflows: existsSync3(join4(config.dataDir, "workflows")),
|
|
2725
|
+
sessions: existsSync3(join4(config.dataDir, SESSIONS_DIR_NAME)),
|
|
2726
|
+
db: existsSync3(join4(config.dataDir, "workflows.db"))
|
|
2703
2727
|
},
|
|
2704
2728
|
store: {
|
|
2705
2729
|
tables: store.listTables(),
|
|
@@ -2812,7 +2836,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2812
2836
|
}
|
|
2813
2837
|
}
|
|
2814
2838
|
case "status": {
|
|
2815
|
-
if (!
|
|
2839
|
+
if (!existsSync3(statusFile)) {
|
|
2816
2840
|
if (opts.json) {
|
|
2817
2841
|
console.log(JSON.stringify({ running: false, lastReap: null }));
|
|
2818
2842
|
} else {
|
|
@@ -2821,7 +2845,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2821
2845
|
return;
|
|
2822
2846
|
}
|
|
2823
2847
|
const lastReap = JSON.parse(readFileSync2(statusFile, "utf8"));
|
|
2824
|
-
const pid =
|
|
2848
|
+
const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
|
|
2825
2849
|
const running = pid !== null && processAlive(Number(pid));
|
|
2826
2850
|
if (opts.json) {
|
|
2827
2851
|
console.log(JSON.stringify({ running, pid, lastReap }, null, 2));
|
|
@@ -2831,7 +2855,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2831
2855
|
return;
|
|
2832
2856
|
}
|
|
2833
2857
|
case "stop": {
|
|
2834
|
-
if (!
|
|
2858
|
+
if (!existsSync3(pidFile)) {
|
|
2835
2859
|
console.error("no daemon pid file \u2014 nothing to stop");
|
|
2836
2860
|
process.exit(1);
|
|
2837
2861
|
}
|
package/dist/index.js
CHANGED
|
@@ -439,13 +439,10 @@ var init_lanes = __esm(() => {
|
|
|
439
439
|
["grok", createGrokAdapter()]
|
|
440
440
|
]);
|
|
441
441
|
});
|
|
442
|
-
|
|
443
|
-
// src/service.ts
|
|
444
|
-
import { join as join3 } from "path";
|
|
445
442
|
// package.json
|
|
446
443
|
var package_default = {
|
|
447
444
|
name: "@hasna/workflows",
|
|
448
|
-
version: "0.1.
|
|
445
|
+
version: "0.1.3",
|
|
449
446
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
450
447
|
type: "module",
|
|
451
448
|
main: "./dist/index.js",
|
|
@@ -515,9 +512,10 @@ var package_default = {
|
|
|
515
512
|
author: "Hasna",
|
|
516
513
|
license: "Apache-2.0",
|
|
517
514
|
dependencies: {
|
|
518
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
515
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
519
516
|
"@cursor/sdk": "^1.0.28",
|
|
520
|
-
"@
|
|
517
|
+
"@hasna/paths": "0.1.0",
|
|
518
|
+
"@openai/codex-sdk": "0.147.0",
|
|
521
519
|
commander: "^13.1.0"
|
|
522
520
|
},
|
|
523
521
|
devDependencies: {
|
|
@@ -1500,9 +1498,9 @@ class WorkflowsDaemon {
|
|
|
1500
1498
|
}
|
|
1501
1499
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1502
1500
|
if (!result.ok) {
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1501
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1502
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1503
|
+
if (shouldRetry) {
|
|
1506
1504
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1507
1505
|
return { kind: "advanced", nodeId };
|
|
1508
1506
|
}
|
|
@@ -1634,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1634
1632
|
return store.getRun(run.id);
|
|
1635
1633
|
}
|
|
1636
1634
|
|
|
1635
|
+
// src/paths.ts
|
|
1636
|
+
import { existsSync } from "fs";
|
|
1637
|
+
import { homedir } from "os";
|
|
1638
|
+
import { join } from "path";
|
|
1639
|
+
import { dataDir } from "@hasna/paths";
|
|
1640
|
+
var APP = "workflows";
|
|
1641
|
+
var STORE_DB_FILE = "workflows.db";
|
|
1642
|
+
function getHomeDir(env = process.env) {
|
|
1643
|
+
return env["HOME"] || env["USERPROFILE"] || homedir();
|
|
1644
|
+
}
|
|
1645
|
+
function getLegacyDataDir(env = process.env) {
|
|
1646
|
+
return join(getHomeDir(env), ".hasna", APP);
|
|
1647
|
+
}
|
|
1648
|
+
function getResolverDataDir(env = process.env) {
|
|
1649
|
+
return dataDir({ app: APP, home: getHomeDir(env), env });
|
|
1650
|
+
}
|
|
1651
|
+
function adoptResolverDataDir(resolved, env = process.env) {
|
|
1652
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
1653
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
1654
|
+
return true;
|
|
1655
|
+
return existsSync(join(resolved, STORE_DB_FILE));
|
|
1656
|
+
}
|
|
1657
|
+
function getEffectiveDataDir(env = process.env) {
|
|
1658
|
+
const resolved = getResolverDataDir(env);
|
|
1659
|
+
return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1637
1662
|
// src/store.ts
|
|
1638
1663
|
import { Database } from "bun:sqlite";
|
|
1639
1664
|
import { mkdirSync } from "fs";
|
|
1640
|
-
import { join } from "path";
|
|
1665
|
+
import { join as join2 } from "path";
|
|
1641
1666
|
import { randomUUID } from "crypto";
|
|
1642
1667
|
function nowIso2() {
|
|
1643
1668
|
return new Date().toISOString();
|
|
1644
1669
|
}
|
|
1645
|
-
function openStore(
|
|
1646
|
-
mkdirSync(
|
|
1647
|
-
const db = new Database(
|
|
1670
|
+
function openStore(dataDir2) {
|
|
1671
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1672
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1648
1673
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1649
1674
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1650
1675
|
db.exec(`
|
|
@@ -1822,8 +1847,8 @@ function openStore(dataDir) {
|
|
|
1822
1847
|
|
|
1823
1848
|
// src/wal.ts
|
|
1824
1849
|
import { createHash as createHash3 } from "crypto";
|
|
1825
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1826
|
-
import { join as
|
|
1850
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1851
|
+
import { join as join3 } from "path";
|
|
1827
1852
|
var WAL_FILE_NAME = "session.wal";
|
|
1828
1853
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1829
1854
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1838,11 +1863,11 @@ class SessionWAL {
|
|
|
1838
1863
|
constructor(filePath) {
|
|
1839
1864
|
this.filePath = filePath;
|
|
1840
1865
|
}
|
|
1841
|
-
static open(
|
|
1842
|
-
mkdirSync2(
|
|
1843
|
-
const canonical =
|
|
1844
|
-
const legacy =
|
|
1845
|
-
if (
|
|
1866
|
+
static open(dataDir2) {
|
|
1867
|
+
mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
|
|
1868
|
+
const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
|
|
1869
|
+
const legacy = join3(dataDir2, WAL_FILE_NAME);
|
|
1870
|
+
if (existsSync2(legacy) && !existsSync2(canonical)) {
|
|
1846
1871
|
try {
|
|
1847
1872
|
renameSync(legacy, canonical);
|
|
1848
1873
|
} catch {
|
|
@@ -1973,11 +1998,10 @@ function envInt(names, fallback) {
|
|
|
1973
1998
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1974
1999
|
}
|
|
1975
2000
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1976
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1977
2001
|
return {
|
|
1978
2002
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1979
2003
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1980
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2004
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1981
2005
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1982
2006
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1983
2007
|
};
|
package/dist/mcp/index.js
CHANGED
|
@@ -1415,9 +1415,9 @@ class WorkflowsDaemon {
|
|
|
1415
1415
|
}
|
|
1416
1416
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1417
1417
|
if (!result.ok) {
|
|
1418
|
-
const
|
|
1419
|
-
|
|
1420
|
-
|
|
1418
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1419
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1420
|
+
if (shouldRetry) {
|
|
1421
1421
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1422
1422
|
return { kind: "advanced", nodeId };
|
|
1423
1423
|
}
|
|
@@ -1974,13 +1974,10 @@ async function callWorkflowTool(service, name, args) {
|
|
|
1974
1974
|
throw new Error(`Unknown tool: ${name}`);
|
|
1975
1975
|
}
|
|
1976
1976
|
}
|
|
1977
|
-
|
|
1978
|
-
// src/service.ts
|
|
1979
|
-
import { join as join3 } from "path";
|
|
1980
1977
|
// package.json
|
|
1981
1978
|
var package_default = {
|
|
1982
1979
|
name: "@hasna/workflows",
|
|
1983
|
-
version: "0.1.
|
|
1980
|
+
version: "0.1.3",
|
|
1984
1981
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
1985
1982
|
type: "module",
|
|
1986
1983
|
main: "./dist/index.js",
|
|
@@ -2050,9 +2047,10 @@ var package_default = {
|
|
|
2050
2047
|
author: "Hasna",
|
|
2051
2048
|
license: "Apache-2.0",
|
|
2052
2049
|
dependencies: {
|
|
2053
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
2050
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
2054
2051
|
"@cursor/sdk": "^1.0.28",
|
|
2055
|
-
"@
|
|
2052
|
+
"@hasna/paths": "0.1.0",
|
|
2053
|
+
"@openai/codex-sdk": "0.147.0",
|
|
2056
2054
|
commander: "^13.1.0"
|
|
2057
2055
|
},
|
|
2058
2056
|
devDependencies: {
|
|
@@ -2064,6 +2062,33 @@ var package_default = {
|
|
|
2064
2062
|
}
|
|
2065
2063
|
};
|
|
2066
2064
|
|
|
2065
|
+
// src/paths.ts
|
|
2066
|
+
import { existsSync as existsSync2 } from "fs";
|
|
2067
|
+
import { homedir } from "os";
|
|
2068
|
+
import { join as join3 } from "path";
|
|
2069
|
+
import { dataDir } from "@hasna/paths";
|
|
2070
|
+
var APP = "workflows";
|
|
2071
|
+
var STORE_DB_FILE = "workflows.db";
|
|
2072
|
+
function getHomeDir(env = process.env) {
|
|
2073
|
+
return env["HOME"] || env["USERPROFILE"] || homedir();
|
|
2074
|
+
}
|
|
2075
|
+
function getLegacyDataDir(env = process.env) {
|
|
2076
|
+
return join3(getHomeDir(env), ".hasna", APP);
|
|
2077
|
+
}
|
|
2078
|
+
function getResolverDataDir(env = process.env) {
|
|
2079
|
+
return dataDir({ app: APP, home: getHomeDir(env), env });
|
|
2080
|
+
}
|
|
2081
|
+
function adoptResolverDataDir(resolved, env = process.env) {
|
|
2082
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
2083
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
2084
|
+
return true;
|
|
2085
|
+
return existsSync2(join3(resolved, STORE_DB_FILE));
|
|
2086
|
+
}
|
|
2087
|
+
function getEffectiveDataDir(env = process.env) {
|
|
2088
|
+
const resolved = getResolverDataDir(env);
|
|
2089
|
+
return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2067
2092
|
// src/service.ts
|
|
2068
2093
|
var DEFAULT_PORT = 8790;
|
|
2069
2094
|
var DEFAULT_HOST = "127.0.0.1";
|
|
@@ -2086,11 +2111,10 @@ function envInt(names, fallback) {
|
|
|
2086
2111
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2087
2112
|
}
|
|
2088
2113
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
2089
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
2090
2114
|
return {
|
|
2091
2115
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
2092
2116
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
2093
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2117
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
2094
2118
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
2095
2119
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
2096
2120
|
};
|
package/dist/serve/index.js
CHANGED
|
@@ -440,13 +440,10 @@ var init_lanes = __esm(() => {
|
|
|
440
440
|
["grok", createGrokAdapter()]
|
|
441
441
|
]);
|
|
442
442
|
});
|
|
443
|
-
|
|
444
|
-
// src/service.ts
|
|
445
|
-
import { join as join3 } from "path";
|
|
446
443
|
// package.json
|
|
447
444
|
var package_default = {
|
|
448
445
|
name: "@hasna/workflows",
|
|
449
|
-
version: "0.1.
|
|
446
|
+
version: "0.1.3",
|
|
450
447
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
451
448
|
type: "module",
|
|
452
449
|
main: "./dist/index.js",
|
|
@@ -516,9 +513,10 @@ var package_default = {
|
|
|
516
513
|
author: "Hasna",
|
|
517
514
|
license: "Apache-2.0",
|
|
518
515
|
dependencies: {
|
|
519
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
516
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
520
517
|
"@cursor/sdk": "^1.0.28",
|
|
521
|
-
"@
|
|
518
|
+
"@hasna/paths": "0.1.0",
|
|
519
|
+
"@openai/codex-sdk": "0.147.0",
|
|
522
520
|
commander: "^13.1.0"
|
|
523
521
|
},
|
|
524
522
|
devDependencies: {
|
|
@@ -1501,9 +1499,9 @@ class WorkflowsDaemon {
|
|
|
1501
1499
|
}
|
|
1502
1500
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1503
1501
|
if (!result.ok) {
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1502
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1503
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1504
|
+
if (shouldRetry) {
|
|
1507
1505
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1508
1506
|
return { kind: "advanced", nodeId };
|
|
1509
1507
|
}
|
|
@@ -1635,17 +1633,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1635
1633
|
return store.getRun(run.id);
|
|
1636
1634
|
}
|
|
1637
1635
|
|
|
1636
|
+
// src/paths.ts
|
|
1637
|
+
import { existsSync } from "fs";
|
|
1638
|
+
import { homedir } from "os";
|
|
1639
|
+
import { join } from "path";
|
|
1640
|
+
import { dataDir } from "@hasna/paths";
|
|
1641
|
+
var APP = "workflows";
|
|
1642
|
+
var STORE_DB_FILE = "workflows.db";
|
|
1643
|
+
function getHomeDir(env = process.env) {
|
|
1644
|
+
return env["HOME"] || env["USERPROFILE"] || homedir();
|
|
1645
|
+
}
|
|
1646
|
+
function getLegacyDataDir(env = process.env) {
|
|
1647
|
+
return join(getHomeDir(env), ".hasna", APP);
|
|
1648
|
+
}
|
|
1649
|
+
function getResolverDataDir(env = process.env) {
|
|
1650
|
+
return dataDir({ app: APP, home: getHomeDir(env), env });
|
|
1651
|
+
}
|
|
1652
|
+
function adoptResolverDataDir(resolved, env = process.env) {
|
|
1653
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
1654
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
1655
|
+
return true;
|
|
1656
|
+
return existsSync(join(resolved, STORE_DB_FILE));
|
|
1657
|
+
}
|
|
1658
|
+
function getEffectiveDataDir(env = process.env) {
|
|
1659
|
+
const resolved = getResolverDataDir(env);
|
|
1660
|
+
return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1638
1663
|
// src/store.ts
|
|
1639
1664
|
import { Database } from "bun:sqlite";
|
|
1640
1665
|
import { mkdirSync } from "fs";
|
|
1641
|
-
import { join } from "path";
|
|
1666
|
+
import { join as join2 } from "path";
|
|
1642
1667
|
import { randomUUID } from "crypto";
|
|
1643
1668
|
function nowIso2() {
|
|
1644
1669
|
return new Date().toISOString();
|
|
1645
1670
|
}
|
|
1646
|
-
function openStore(
|
|
1647
|
-
mkdirSync(
|
|
1648
|
-
const db = new Database(
|
|
1671
|
+
function openStore(dataDir2) {
|
|
1672
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1673
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1649
1674
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1650
1675
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1651
1676
|
db.exec(`
|
|
@@ -1823,8 +1848,8 @@ function openStore(dataDir) {
|
|
|
1823
1848
|
|
|
1824
1849
|
// src/wal.ts
|
|
1825
1850
|
import { createHash as createHash3 } from "crypto";
|
|
1826
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1827
|
-
import { join as
|
|
1851
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1852
|
+
import { join as join3 } from "path";
|
|
1828
1853
|
var WAL_FILE_NAME = "session.wal";
|
|
1829
1854
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1830
1855
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1839,11 +1864,11 @@ class SessionWAL {
|
|
|
1839
1864
|
constructor(filePath) {
|
|
1840
1865
|
this.filePath = filePath;
|
|
1841
1866
|
}
|
|
1842
|
-
static open(
|
|
1843
|
-
mkdirSync2(
|
|
1844
|
-
const canonical =
|
|
1845
|
-
const legacy =
|
|
1846
|
-
if (
|
|
1867
|
+
static open(dataDir2) {
|
|
1868
|
+
mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
|
|
1869
|
+
const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
|
|
1870
|
+
const legacy = join3(dataDir2, WAL_FILE_NAME);
|
|
1871
|
+
if (existsSync2(legacy) && !existsSync2(canonical)) {
|
|
1847
1872
|
try {
|
|
1848
1873
|
renameSync(legacy, canonical);
|
|
1849
1874
|
} catch {
|
|
@@ -1974,11 +1999,10 @@ function envInt(names, fallback) {
|
|
|
1974
1999
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1975
2000
|
}
|
|
1976
2001
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1977
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1978
2002
|
return {
|
|
1979
2003
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1980
2004
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1981
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2005
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1982
2006
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1983
2007
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1984
2008
|
};
|
package/dist/service.js
CHANGED
|
@@ -439,13 +439,10 @@ var init_lanes = __esm(() => {
|
|
|
439
439
|
["grok", createGrokAdapter()]
|
|
440
440
|
]);
|
|
441
441
|
});
|
|
442
|
-
|
|
443
|
-
// src/service.ts
|
|
444
|
-
import { join as join3 } from "path";
|
|
445
442
|
// package.json
|
|
446
443
|
var package_default = {
|
|
447
444
|
name: "@hasna/workflows",
|
|
448
|
-
version: "0.1.
|
|
445
|
+
version: "0.1.3",
|
|
449
446
|
description: "Universal graph workflow app \u2014 graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
450
447
|
type: "module",
|
|
451
448
|
main: "./dist/index.js",
|
|
@@ -515,9 +512,10 @@ var package_default = {
|
|
|
515
512
|
author: "Hasna",
|
|
516
513
|
license: "Apache-2.0",
|
|
517
514
|
dependencies: {
|
|
518
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
515
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
519
516
|
"@cursor/sdk": "^1.0.28",
|
|
520
|
-
"@
|
|
517
|
+
"@hasna/paths": "0.1.0",
|
|
518
|
+
"@openai/codex-sdk": "0.147.0",
|
|
521
519
|
commander: "^13.1.0"
|
|
522
520
|
},
|
|
523
521
|
devDependencies: {
|
|
@@ -1500,9 +1498,9 @@ class WorkflowsDaemon {
|
|
|
1500
1498
|
}
|
|
1501
1499
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1502
1500
|
if (!result.ok) {
|
|
1503
|
-
const
|
|
1504
|
-
|
|
1505
|
-
|
|
1501
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1502
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1503
|
+
if (shouldRetry) {
|
|
1506
1504
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1507
1505
|
return { kind: "advanced", nodeId };
|
|
1508
1506
|
}
|
|
@@ -1634,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1634
1632
|
return store.getRun(run.id);
|
|
1635
1633
|
}
|
|
1636
1634
|
|
|
1635
|
+
// src/paths.ts
|
|
1636
|
+
import { existsSync } from "fs";
|
|
1637
|
+
import { homedir } from "os";
|
|
1638
|
+
import { join } from "path";
|
|
1639
|
+
import { dataDir } from "@hasna/paths";
|
|
1640
|
+
var APP = "workflows";
|
|
1641
|
+
var STORE_DB_FILE = "workflows.db";
|
|
1642
|
+
function getHomeDir(env = process.env) {
|
|
1643
|
+
return env["HOME"] || env["USERPROFILE"] || homedir();
|
|
1644
|
+
}
|
|
1645
|
+
function getLegacyDataDir(env = process.env) {
|
|
1646
|
+
return join(getHomeDir(env), ".hasna", APP);
|
|
1647
|
+
}
|
|
1648
|
+
function getResolverDataDir(env = process.env) {
|
|
1649
|
+
return dataDir({ app: APP, home: getHomeDir(env), env });
|
|
1650
|
+
}
|
|
1651
|
+
function adoptResolverDataDir(resolved, env = process.env) {
|
|
1652
|
+
const dataOverride = env.HASNA_DATA_HOME;
|
|
1653
|
+
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
1654
|
+
return true;
|
|
1655
|
+
return existsSync(join(resolved, STORE_DB_FILE));
|
|
1656
|
+
}
|
|
1657
|
+
function getEffectiveDataDir(env = process.env) {
|
|
1658
|
+
const resolved = getResolverDataDir(env);
|
|
1659
|
+
return adoptResolverDataDir(resolved, env) ? resolved : getLegacyDataDir(env);
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1637
1662
|
// src/store.ts
|
|
1638
1663
|
import { Database } from "bun:sqlite";
|
|
1639
1664
|
import { mkdirSync } from "fs";
|
|
1640
|
-
import { join } from "path";
|
|
1665
|
+
import { join as join2 } from "path";
|
|
1641
1666
|
import { randomUUID } from "crypto";
|
|
1642
1667
|
function nowIso2() {
|
|
1643
1668
|
return new Date().toISOString();
|
|
1644
1669
|
}
|
|
1645
|
-
function openStore(
|
|
1646
|
-
mkdirSync(
|
|
1647
|
-
const db = new Database(
|
|
1670
|
+
function openStore(dataDir2) {
|
|
1671
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1672
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1648
1673
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1649
1674
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1650
1675
|
db.exec(`
|
|
@@ -1822,8 +1847,8 @@ function openStore(dataDir) {
|
|
|
1822
1847
|
|
|
1823
1848
|
// src/wal.ts
|
|
1824
1849
|
import { createHash as createHash3 } from "crypto";
|
|
1825
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1826
|
-
import { join as
|
|
1850
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1851
|
+
import { join as join3 } from "path";
|
|
1827
1852
|
var WAL_FILE_NAME = "session.wal";
|
|
1828
1853
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1829
1854
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1838,11 +1863,11 @@ class SessionWAL {
|
|
|
1838
1863
|
constructor(filePath) {
|
|
1839
1864
|
this.filePath = filePath;
|
|
1840
1865
|
}
|
|
1841
|
-
static open(
|
|
1842
|
-
mkdirSync2(
|
|
1843
|
-
const canonical =
|
|
1844
|
-
const legacy =
|
|
1845
|
-
if (
|
|
1866
|
+
static open(dataDir2) {
|
|
1867
|
+
mkdirSync2(join3(dataDir2, SESSIONS_DIR_NAME), { recursive: true });
|
|
1868
|
+
const canonical = join3(dataDir2, SESSIONS_DIR_NAME, WAL_FILE_NAME);
|
|
1869
|
+
const legacy = join3(dataDir2, WAL_FILE_NAME);
|
|
1870
|
+
if (existsSync2(legacy) && !existsSync2(canonical)) {
|
|
1846
1871
|
try {
|
|
1847
1872
|
renameSync(legacy, canonical);
|
|
1848
1873
|
} catch {
|
|
@@ -1973,11 +1998,10 @@ function envInt(names, fallback) {
|
|
|
1973
1998
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1974
1999
|
}
|
|
1975
2000
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1976
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1977
2001
|
return {
|
|
1978
2002
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1979
2003
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1980
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2004
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1981
2005
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1982
2006
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1983
2007
|
};
|
package/hasna.contract.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"class": "service",
|
|
5
5
|
"contractVersion": "v1",
|
|
6
6
|
"kitVersion": "0.14.1",
|
|
7
|
-
"description": "Universal graph workflow app: graph language (start/step/decision/while/end), three-table run store, session WAL, claims/leases daemon, four lane adapters (claude, codex, cursor, grok), and a full CLI on four surfaces (CLI, MCP, serve, SDK).",
|
|
7
|
+
"description": "Universal graph workflow app: graph language (start/step/decision/while/end), three-table run store, session WAL, claims/leases daemon, four lane adapters (claude, codex, cursor, grok), and a full CLI on four surfaces (CLI, MCP, serve, SDK). Local state lives at the effective workflows data home (~/.hasna/workflows by default, resolved via @hasna/paths; the XDG data home is adopted once the store is migrated there or HASNA_DATA_HOME is set).",
|
|
8
8
|
"bins": [
|
|
9
9
|
"workflows",
|
|
10
10
|
"workflows-mcp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/workflows",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Universal graph workflow app — graph language (start/step/decision/while/end), three-table store, session WAL, daemon with claims/leases, four lane adapters (claude/codex/cursor/grok), and a full CLI on four surfaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -70,9 +70,10 @@
|
|
|
70
70
|
"author": "Hasna",
|
|
71
71
|
"license": "Apache-2.0",
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
73
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
74
74
|
"@cursor/sdk": "^1.0.28",
|
|
75
|
-
"@
|
|
75
|
+
"@hasna/paths": "0.1.0",
|
|
76
|
+
"@openai/codex-sdk": "0.147.0",
|
|
76
77
|
"commander": "^13.1.0"
|
|
77
78
|
},
|
|
78
79
|
"devDependencies": {
|
package/types/paths.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflows data-home resolution — the effective local data home for the
|
|
3
|
+
* @hasna/workflows package, resolved through @hasna/paths.
|
|
4
|
+
*
|
|
5
|
+
* Resolution order:
|
|
6
|
+
* 1. exact-app override — `HASNA_WORKFLOWS_DATA_DIR` / `WORKFLOWS_DATA_DIR`
|
|
7
|
+
* (the shipped overrides, applied in `resolveWorkflowsConfig`) — wins
|
|
8
|
+
* unconditionally;
|
|
9
|
+
* 2. the @hasna/paths data home (XDG / macOS home layout) once adopted;
|
|
10
|
+
* 3. otherwise the legacy `~/.hasna/workflows` default.
|
|
11
|
+
*
|
|
12
|
+
* Gated legacy adoption: the legacy `~/.hasna/workflows` home stays effective
|
|
13
|
+
* until the store has actually been migrated to the XDG data home
|
|
14
|
+
* (`workflows.db` exists at the resolver home) or the operator sets the
|
|
15
|
+
* data-kind override `HASNA_DATA_HOME`. An existing local store never becomes
|
|
16
|
+
* invisible on upgrade.
|
|
17
|
+
*/
|
|
18
|
+
/** The workflows app slug used by the @hasna/paths resolver. */
|
|
19
|
+
export declare const APP: "workflows";
|
|
20
|
+
/** The store database file name, the marker that a store lives at a data home. */
|
|
21
|
+
export declare const STORE_DB_FILE = "workflows.db";
|
|
22
|
+
/**
|
|
23
|
+
* The effective user home, mirroring the pre-existing resolution
|
|
24
|
+
* (`HOME` || `USERPROFILE` || os.homedir()).
|
|
25
|
+
*/
|
|
26
|
+
export declare function getHomeDir(env?: NodeJS.ProcessEnv): string;
|
|
27
|
+
/** Pre-XDG legacy workflows data home: ~/.hasna/workflows. */
|
|
28
|
+
export declare function getLegacyDataDir(env?: NodeJS.ProcessEnv): string;
|
|
29
|
+
/**
|
|
30
|
+
* The @hasna/paths-resolved workflows data home (XDG / macOS home layout):
|
|
31
|
+
* `~/.local/share/hasna/workflows` on Linux, `~/Library/Application
|
|
32
|
+
* Support/Hasna/workflows` on macOS. The home override mirrors the
|
|
33
|
+
* pre-existing `$HOME`-first resolution so the resolver follows the same home
|
|
34
|
+
* the legacy path does.
|
|
35
|
+
*/
|
|
36
|
+
export declare function getResolverDataDir(env?: NodeJS.ProcessEnv): string;
|
|
37
|
+
/**
|
|
38
|
+
* Whether the resolver (XDG) data home should be adopted as the effective
|
|
39
|
+
* workflows data home. The resolver home is adopted only when the operator
|
|
40
|
+
* has set `HASNA_DATA_HOME` (the data-kind override — a deliberate opt-in to
|
|
41
|
+
* the XDG layout) or the store has already been physically migrated there
|
|
42
|
+
* (`workflows.db` exists). A machine that only redirects another kind (e.g.
|
|
43
|
+
* cache to tmpfs) must NOT have its data home moved, and a live store at the
|
|
44
|
+
* legacy home must never become invisible on upgrade.
|
|
45
|
+
*/
|
|
46
|
+
export declare function adoptResolverDataDir(resolved: string, env?: NodeJS.ProcessEnv): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* The effective workflows data home: the resolver home once adopted,
|
|
49
|
+
* otherwise the legacy `~/.hasna/workflows` default.
|
|
50
|
+
*/
|
|
51
|
+
export declare function getEffectiveDataDir(env?: NodeJS.ProcessEnv): string;
|
package/types/types.d.ts
CHANGED
|
@@ -12,7 +12,9 @@ export interface WorkflowsConfig {
|
|
|
12
12
|
port: number;
|
|
13
13
|
/** Bind host for workflows-serve. Default 127.0.0.1. */
|
|
14
14
|
host: string;
|
|
15
|
-
/** Data directory (store slice lands the db here). Default ~/.hasna/workflows
|
|
15
|
+
/** Data directory (store slice lands the db here). Default ~/.hasna/workflows,
|
|
16
|
+
* resolved through @hasna/paths once the XDG data home is adopted
|
|
17
|
+
* (HASNA_DATA_HOME set or the store migrated there). */
|
|
16
18
|
dataDir: string;
|
|
17
19
|
/** Optional client API URL (WORKFLOWS_API_URL) — consumed by later slices. */
|
|
18
20
|
apiUrl?: string;
|