@hasna/workflows 0.1.1 → 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 +56 -33
- package/dist/index.js +47 -24
- package/dist/mcp/index.js +36 -13
- package/dist/serve/index.js +47 -24
- package/dist/service.js +47 -24
- package/hasna.contract.json +1 -1
- package/package.json +5 -5
- 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",
|
|
@@ -517,15 +514,15 @@ var package_default = {
|
|
|
517
514
|
url: "https://github.com/hasna/apps/issues"
|
|
518
515
|
},
|
|
519
516
|
engines: {
|
|
520
|
-
bun: ">=1.0.0"
|
|
521
|
-
node: ">=20.0.0"
|
|
517
|
+
bun: ">=1.0.0"
|
|
522
518
|
},
|
|
523
519
|
author: "Hasna",
|
|
524
520
|
license: "Apache-2.0",
|
|
525
521
|
dependencies: {
|
|
526
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
522
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
527
523
|
"@cursor/sdk": "^1.0.28",
|
|
528
|
-
"@
|
|
524
|
+
"@hasna/paths": "0.1.0",
|
|
525
|
+
"@openai/codex-sdk": "0.147.0",
|
|
529
526
|
commander: "^13.1.0"
|
|
530
527
|
},
|
|
531
528
|
devDependencies: {
|
|
@@ -1536,9 +1533,9 @@ class WorkflowsDaemon {
|
|
|
1536
1533
|
}
|
|
1537
1534
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1538
1535
|
if (!result.ok) {
|
|
1539
|
-
const
|
|
1540
|
-
|
|
1541
|
-
|
|
1536
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1537
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1538
|
+
if (shouldRetry) {
|
|
1542
1539
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1543
1540
|
return { kind: "advanced", nodeId };
|
|
1544
1541
|
}
|
|
@@ -1670,17 +1667,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1670
1667
|
return store.getRun(run.id);
|
|
1671
1668
|
}
|
|
1672
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
|
+
|
|
1673
1697
|
// src/store.ts
|
|
1674
1698
|
import { Database } from "bun:sqlite";
|
|
1675
1699
|
import { mkdirSync } from "fs";
|
|
1676
|
-
import { join } from "path";
|
|
1700
|
+
import { join as join2 } from "path";
|
|
1677
1701
|
import { randomUUID } from "crypto";
|
|
1678
1702
|
function nowIso2() {
|
|
1679
1703
|
return new Date().toISOString();
|
|
1680
1704
|
}
|
|
1681
|
-
function openStore(
|
|
1682
|
-
mkdirSync(
|
|
1683
|
-
const db = new Database(
|
|
1705
|
+
function openStore(dataDir2) {
|
|
1706
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1707
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1684
1708
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1685
1709
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1686
1710
|
db.exec(`
|
|
@@ -1858,8 +1882,8 @@ function openStore(dataDir) {
|
|
|
1858
1882
|
|
|
1859
1883
|
// src/wal.ts
|
|
1860
1884
|
import { createHash as createHash3 } from "crypto";
|
|
1861
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1862
|
-
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";
|
|
1863
1887
|
var WAL_FILE_NAME = "session.wal";
|
|
1864
1888
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1865
1889
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1874,11 +1898,11 @@ class SessionWAL {
|
|
|
1874
1898
|
constructor(filePath) {
|
|
1875
1899
|
this.filePath = filePath;
|
|
1876
1900
|
}
|
|
1877
|
-
static open(
|
|
1878
|
-
mkdirSync2(
|
|
1879
|
-
const canonical =
|
|
1880
|
-
const legacy =
|
|
1881
|
-
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)) {
|
|
1882
1906
|
try {
|
|
1883
1907
|
renameSync(legacy, canonical);
|
|
1884
1908
|
} catch {
|
|
@@ -2009,11 +2033,10 @@ function envInt(names, fallback) {
|
|
|
2009
2033
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2010
2034
|
}
|
|
2011
2035
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
2012
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
2013
2036
|
return {
|
|
2014
2037
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
2015
2038
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
2016
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2039
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
2017
2040
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
2018
2041
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
2019
2042
|
};
|
|
@@ -2404,7 +2427,7 @@ program.command("init").description("Create the store layout (workflows/, sessio
|
|
|
2404
2427
|
const store = openStore(config.dataDir);
|
|
2405
2428
|
store.close();
|
|
2406
2429
|
const target = file ?? join4(config.dataDir, "workflows", "demo.json");
|
|
2407
|
-
if (
|
|
2430
|
+
if (existsSync3(target)) {
|
|
2408
2431
|
console.error(`refusing to overwrite existing file ${target}`);
|
|
2409
2432
|
process.exit(1);
|
|
2410
2433
|
}
|
|
@@ -2691,16 +2714,16 @@ program.command("machines").description("List observed machines or report this m
|
|
|
2691
2714
|
case "status": {
|
|
2692
2715
|
const replay = wal.replay();
|
|
2693
2716
|
const pidFile = join4(config.dataDir, "daemon.pid");
|
|
2694
|
-
const pid =
|
|
2717
|
+
const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
|
|
2695
2718
|
const running = pid !== null && processAlive(Number(pid));
|
|
2696
2719
|
const report = {
|
|
2697
2720
|
hostname: hostname(),
|
|
2698
2721
|
pid: process.pid,
|
|
2699
2722
|
dataDir: config.dataDir,
|
|
2700
2723
|
layout: {
|
|
2701
|
-
workflows:
|
|
2702
|
-
sessions:
|
|
2703
|
-
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"))
|
|
2704
2727
|
},
|
|
2705
2728
|
store: {
|
|
2706
2729
|
tables: store.listTables(),
|
|
@@ -2813,7 +2836,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2813
2836
|
}
|
|
2814
2837
|
}
|
|
2815
2838
|
case "status": {
|
|
2816
|
-
if (!
|
|
2839
|
+
if (!existsSync3(statusFile)) {
|
|
2817
2840
|
if (opts.json) {
|
|
2818
2841
|
console.log(JSON.stringify({ running: false, lastReap: null }));
|
|
2819
2842
|
} else {
|
|
@@ -2822,7 +2845,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2822
2845
|
return;
|
|
2823
2846
|
}
|
|
2824
2847
|
const lastReap = JSON.parse(readFileSync2(statusFile, "utf8"));
|
|
2825
|
-
const pid =
|
|
2848
|
+
const pid = existsSync3(pidFile) ? readFileSync2(pidFile, "utf8").trim() : null;
|
|
2826
2849
|
const running = pid !== null && processAlive(Number(pid));
|
|
2827
2850
|
if (opts.json) {
|
|
2828
2851
|
console.log(JSON.stringify({ running, pid, lastReap }, null, 2));
|
|
@@ -2832,7 +2855,7 @@ program.command("daemon").description("Run or inspect the daemon").argument("<ac
|
|
|
2832
2855
|
return;
|
|
2833
2856
|
}
|
|
2834
2857
|
case "stop": {
|
|
2835
|
-
if (!
|
|
2858
|
+
if (!existsSync3(pidFile)) {
|
|
2836
2859
|
console.error("no daemon pid file \u2014 nothing to stop");
|
|
2837
2860
|
process.exit(1);
|
|
2838
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",
|
|
@@ -510,15 +507,15 @@ var package_default = {
|
|
|
510
507
|
url: "https://github.com/hasna/apps/issues"
|
|
511
508
|
},
|
|
512
509
|
engines: {
|
|
513
|
-
bun: ">=1.0.0"
|
|
514
|
-
node: ">=20.0.0"
|
|
510
|
+
bun: ">=1.0.0"
|
|
515
511
|
},
|
|
516
512
|
author: "Hasna",
|
|
517
513
|
license: "Apache-2.0",
|
|
518
514
|
dependencies: {
|
|
519
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
515
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
520
516
|
"@cursor/sdk": "^1.0.28",
|
|
521
|
-
"@
|
|
517
|
+
"@hasna/paths": "0.1.0",
|
|
518
|
+
"@openai/codex-sdk": "0.147.0",
|
|
522
519
|
commander: "^13.1.0"
|
|
523
520
|
},
|
|
524
521
|
devDependencies: {
|
|
@@ -1501,9 +1498,9 @@ class WorkflowsDaemon {
|
|
|
1501
1498
|
}
|
|
1502
1499
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1503
1500
|
if (!result.ok) {
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1501
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1502
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1503
|
+
if (shouldRetry) {
|
|
1507
1504
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1508
1505
|
return { kind: "advanced", nodeId };
|
|
1509
1506
|
}
|
|
@@ -1635,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1635
1632
|
return store.getRun(run.id);
|
|
1636
1633
|
}
|
|
1637
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
|
+
|
|
1638
1662
|
// src/store.ts
|
|
1639
1663
|
import { Database } from "bun:sqlite";
|
|
1640
1664
|
import { mkdirSync } from "fs";
|
|
1641
|
-
import { join } from "path";
|
|
1665
|
+
import { join as join2 } from "path";
|
|
1642
1666
|
import { randomUUID } from "crypto";
|
|
1643
1667
|
function nowIso2() {
|
|
1644
1668
|
return new Date().toISOString();
|
|
1645
1669
|
}
|
|
1646
|
-
function openStore(
|
|
1647
|
-
mkdirSync(
|
|
1648
|
-
const db = new Database(
|
|
1670
|
+
function openStore(dataDir2) {
|
|
1671
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1672
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1649
1673
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1650
1674
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1651
1675
|
db.exec(`
|
|
@@ -1823,8 +1847,8 @@ function openStore(dataDir) {
|
|
|
1823
1847
|
|
|
1824
1848
|
// src/wal.ts
|
|
1825
1849
|
import { createHash as createHash3 } from "crypto";
|
|
1826
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1827
|
-
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";
|
|
1828
1852
|
var WAL_FILE_NAME = "session.wal";
|
|
1829
1853
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1830
1854
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1839,11 +1863,11 @@ class SessionWAL {
|
|
|
1839
1863
|
constructor(filePath) {
|
|
1840
1864
|
this.filePath = filePath;
|
|
1841
1865
|
}
|
|
1842
|
-
static open(
|
|
1843
|
-
mkdirSync2(
|
|
1844
|
-
const canonical =
|
|
1845
|
-
const legacy =
|
|
1846
|
-
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)) {
|
|
1847
1871
|
try {
|
|
1848
1872
|
renameSync(legacy, canonical);
|
|
1849
1873
|
} catch {
|
|
@@ -1974,11 +1998,10 @@ function envInt(names, fallback) {
|
|
|
1974
1998
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1975
1999
|
}
|
|
1976
2000
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1977
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1978
2001
|
return {
|
|
1979
2002
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1980
2003
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1981
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2004
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1982
2005
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1983
2006
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1984
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",
|
|
@@ -2045,15 +2042,15 @@ var package_default = {
|
|
|
2045
2042
|
url: "https://github.com/hasna/apps/issues"
|
|
2046
2043
|
},
|
|
2047
2044
|
engines: {
|
|
2048
|
-
bun: ">=1.0.0"
|
|
2049
|
-
node: ">=20.0.0"
|
|
2045
|
+
bun: ">=1.0.0"
|
|
2050
2046
|
},
|
|
2051
2047
|
author: "Hasna",
|
|
2052
2048
|
license: "Apache-2.0",
|
|
2053
2049
|
dependencies: {
|
|
2054
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
2050
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
2055
2051
|
"@cursor/sdk": "^1.0.28",
|
|
2056
|
-
"@
|
|
2052
|
+
"@hasna/paths": "0.1.0",
|
|
2053
|
+
"@openai/codex-sdk": "0.147.0",
|
|
2057
2054
|
commander: "^13.1.0"
|
|
2058
2055
|
},
|
|
2059
2056
|
devDependencies: {
|
|
@@ -2065,6 +2062,33 @@ var package_default = {
|
|
|
2065
2062
|
}
|
|
2066
2063
|
};
|
|
2067
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
|
+
|
|
2068
2092
|
// src/service.ts
|
|
2069
2093
|
var DEFAULT_PORT = 8790;
|
|
2070
2094
|
var DEFAULT_HOST = "127.0.0.1";
|
|
@@ -2087,11 +2111,10 @@ function envInt(names, fallback) {
|
|
|
2087
2111
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
2088
2112
|
}
|
|
2089
2113
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
2090
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
2091
2114
|
return {
|
|
2092
2115
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
2093
2116
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
2094
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2117
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
2095
2118
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
2096
2119
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
2097
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",
|
|
@@ -511,15 +508,15 @@ var package_default = {
|
|
|
511
508
|
url: "https://github.com/hasna/apps/issues"
|
|
512
509
|
},
|
|
513
510
|
engines: {
|
|
514
|
-
bun: ">=1.0.0"
|
|
515
|
-
node: ">=20.0.0"
|
|
511
|
+
bun: ">=1.0.0"
|
|
516
512
|
},
|
|
517
513
|
author: "Hasna",
|
|
518
514
|
license: "Apache-2.0",
|
|
519
515
|
dependencies: {
|
|
520
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
516
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
521
517
|
"@cursor/sdk": "^1.0.28",
|
|
522
|
-
"@
|
|
518
|
+
"@hasna/paths": "0.1.0",
|
|
519
|
+
"@openai/codex-sdk": "0.147.0",
|
|
523
520
|
commander: "^13.1.0"
|
|
524
521
|
},
|
|
525
522
|
devDependencies: {
|
|
@@ -1502,9 +1499,9 @@ class WorkflowsDaemon {
|
|
|
1502
1499
|
}
|
|
1503
1500
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1504
1501
|
if (!result.ok) {
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1502
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1503
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1504
|
+
if (shouldRetry) {
|
|
1508
1505
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1509
1506
|
return { kind: "advanced", nodeId };
|
|
1510
1507
|
}
|
|
@@ -1636,17 +1633,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1636
1633
|
return store.getRun(run.id);
|
|
1637
1634
|
}
|
|
1638
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
|
+
|
|
1639
1663
|
// src/store.ts
|
|
1640
1664
|
import { Database } from "bun:sqlite";
|
|
1641
1665
|
import { mkdirSync } from "fs";
|
|
1642
|
-
import { join } from "path";
|
|
1666
|
+
import { join as join2 } from "path";
|
|
1643
1667
|
import { randomUUID } from "crypto";
|
|
1644
1668
|
function nowIso2() {
|
|
1645
1669
|
return new Date().toISOString();
|
|
1646
1670
|
}
|
|
1647
|
-
function openStore(
|
|
1648
|
-
mkdirSync(
|
|
1649
|
-
const db = new Database(
|
|
1671
|
+
function openStore(dataDir2) {
|
|
1672
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1673
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1650
1674
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1651
1675
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1652
1676
|
db.exec(`
|
|
@@ -1824,8 +1848,8 @@ function openStore(dataDir) {
|
|
|
1824
1848
|
|
|
1825
1849
|
// src/wal.ts
|
|
1826
1850
|
import { createHash as createHash3 } from "crypto";
|
|
1827
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1828
|
-
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";
|
|
1829
1853
|
var WAL_FILE_NAME = "session.wal";
|
|
1830
1854
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1831
1855
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1840,11 +1864,11 @@ class SessionWAL {
|
|
|
1840
1864
|
constructor(filePath) {
|
|
1841
1865
|
this.filePath = filePath;
|
|
1842
1866
|
}
|
|
1843
|
-
static open(
|
|
1844
|
-
mkdirSync2(
|
|
1845
|
-
const canonical =
|
|
1846
|
-
const legacy =
|
|
1847
|
-
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)) {
|
|
1848
1872
|
try {
|
|
1849
1873
|
renameSync(legacy, canonical);
|
|
1850
1874
|
} catch {
|
|
@@ -1975,11 +1999,10 @@ function envInt(names, fallback) {
|
|
|
1975
1999
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1976
2000
|
}
|
|
1977
2001
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1978
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1979
2002
|
return {
|
|
1980
2003
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1981
2004
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1982
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2005
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1983
2006
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1984
2007
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1985
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",
|
|
@@ -510,15 +507,15 @@ var package_default = {
|
|
|
510
507
|
url: "https://github.com/hasna/apps/issues"
|
|
511
508
|
},
|
|
512
509
|
engines: {
|
|
513
|
-
bun: ">=1.0.0"
|
|
514
|
-
node: ">=20.0.0"
|
|
510
|
+
bun: ">=1.0.0"
|
|
515
511
|
},
|
|
516
512
|
author: "Hasna",
|
|
517
513
|
license: "Apache-2.0",
|
|
518
514
|
dependencies: {
|
|
519
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
515
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
520
516
|
"@cursor/sdk": "^1.0.28",
|
|
521
|
-
"@
|
|
517
|
+
"@hasna/paths": "0.1.0",
|
|
518
|
+
"@openai/codex-sdk": "0.147.0",
|
|
522
519
|
commander: "^13.1.0"
|
|
523
520
|
},
|
|
524
521
|
devDependencies: {
|
|
@@ -1501,9 +1498,9 @@ class WorkflowsDaemon {
|
|
|
1501
1498
|
}
|
|
1502
1499
|
const output = { ok: result.ok, exitCode: result.exitCode, output: result.output, durationMs: result.durationMs };
|
|
1503
1500
|
if (!result.ok) {
|
|
1504
|
-
const
|
|
1505
|
-
|
|
1506
|
-
|
|
1501
|
+
const shouldRetry = nodeRow.attempts < (node.maxRetries ?? 0);
|
|
1502
|
+
this.store.bumpAttemptsNode(nodeRow.id);
|
|
1503
|
+
if (shouldRetry) {
|
|
1507
1504
|
this.store.setRunNodeStatus(nodeRow.id, "pending", { error: result.error ?? `exit ${result.exitCode}` });
|
|
1508
1505
|
return { kind: "advanced", nodeId };
|
|
1509
1506
|
}
|
|
@@ -1635,17 +1632,44 @@ async function runGraphToCompletion(store, wal, graph, context, options = {}) {
|
|
|
1635
1632
|
return store.getRun(run.id);
|
|
1636
1633
|
}
|
|
1637
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
|
+
|
|
1638
1662
|
// src/store.ts
|
|
1639
1663
|
import { Database } from "bun:sqlite";
|
|
1640
1664
|
import { mkdirSync } from "fs";
|
|
1641
|
-
import { join } from "path";
|
|
1665
|
+
import { join as join2 } from "path";
|
|
1642
1666
|
import { randomUUID } from "crypto";
|
|
1643
1667
|
function nowIso2() {
|
|
1644
1668
|
return new Date().toISOString();
|
|
1645
1669
|
}
|
|
1646
|
-
function openStore(
|
|
1647
|
-
mkdirSync(
|
|
1648
|
-
const db = new Database(
|
|
1670
|
+
function openStore(dataDir2) {
|
|
1671
|
+
mkdirSync(dataDir2, { recursive: true });
|
|
1672
|
+
const db = new Database(join2(dataDir2, "workflows.db"));
|
|
1649
1673
|
db.exec("PRAGMA journal_mode = WAL");
|
|
1650
1674
|
db.exec("PRAGMA foreign_keys = ON");
|
|
1651
1675
|
db.exec(`
|
|
@@ -1823,8 +1847,8 @@ function openStore(dataDir) {
|
|
|
1823
1847
|
|
|
1824
1848
|
// src/wal.ts
|
|
1825
1849
|
import { createHash as createHash3 } from "crypto";
|
|
1826
|
-
import { appendFileSync, existsSync, mkdirSync as mkdirSync2, readFileSync, renameSync, truncateSync, writeFileSync } from "fs";
|
|
1827
|
-
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";
|
|
1828
1852
|
var WAL_FILE_NAME = "session.wal";
|
|
1829
1853
|
var SESSIONS_DIR_NAME = "sessions";
|
|
1830
1854
|
var MAX_WAL_BYTES = 256 * 1024 * 1024;
|
|
@@ -1839,11 +1863,11 @@ class SessionWAL {
|
|
|
1839
1863
|
constructor(filePath) {
|
|
1840
1864
|
this.filePath = filePath;
|
|
1841
1865
|
}
|
|
1842
|
-
static open(
|
|
1843
|
-
mkdirSync2(
|
|
1844
|
-
const canonical =
|
|
1845
|
-
const legacy =
|
|
1846
|
-
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)) {
|
|
1847
1871
|
try {
|
|
1848
1872
|
renameSync(legacy, canonical);
|
|
1849
1873
|
} catch {
|
|
@@ -1974,11 +1998,10 @@ function envInt(names, fallback) {
|
|
|
1974
1998
|
return Number.isFinite(parsed) ? parsed : fallback;
|
|
1975
1999
|
}
|
|
1976
2000
|
function resolveWorkflowsConfig(overrides = {}) {
|
|
1977
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
|
|
1978
2001
|
return {
|
|
1979
2002
|
port: overrides.port ?? envInt(["HASNA_WORKFLOWS_PORT", "WORKFLOWS_PORT"], DEFAULT_PORT),
|
|
1980
2003
|
host: overrides.host ?? envString(["HASNA_WORKFLOWS_HOST", "WORKFLOWS_HOST"]) ?? DEFAULT_HOST,
|
|
1981
|
-
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ??
|
|
2004
|
+
dataDir: overrides.dataDir ?? envString(["HASNA_WORKFLOWS_DATA_DIR", "WORKFLOWS_DATA_DIR"]) ?? getEffectiveDataDir(),
|
|
1982
2005
|
apiUrl: overrides.apiUrl ?? envString(["WORKFLOWS_API_URL"]),
|
|
1983
2006
|
apiKey: overrides.apiKey ?? envString(["WORKFLOWS_API_KEY"])
|
|
1984
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",
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"url": "https://github.com/hasna/apps/issues"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
68
|
-
"bun": ">=1.0.0"
|
|
69
|
-
"node": ">=20.0.0"
|
|
68
|
+
"bun": ">=1.0.0"
|
|
70
69
|
},
|
|
71
70
|
"author": "Hasna",
|
|
72
71
|
"license": "Apache-2.0",
|
|
73
72
|
"dependencies": {
|
|
74
|
-
"@anthropic-ai/claude-agent-sdk": "
|
|
73
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.234",
|
|
75
74
|
"@cursor/sdk": "^1.0.28",
|
|
76
|
-
"@
|
|
75
|
+
"@hasna/paths": "0.1.0",
|
|
76
|
+
"@openai/codex-sdk": "0.147.0",
|
|
77
77
|
"commander": "^13.1.0"
|
|
78
78
|
},
|
|
79
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;
|