@hasna/loops 0.3.15 → 0.3.17
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 +30 -1
- package/dist/cli/index.js +872 -5
- package/dist/daemon/index.js +60 -2
- package/dist/index.d.ts +3 -1
- package/dist/index.js +582 -2
- package/dist/lib/health.d.ts +70 -0
- package/dist/lib/hygiene.d.ts +62 -0
- package/dist/lib/store.d.ts +1 -0
- package/dist/lib/store.js +43 -0
- package/dist/lib/templates.d.ts +23 -0
- package/dist/sdk/index.js +59 -1
- package/dist/types.d.ts +6 -0
- package/docs/USAGE.md +81 -1
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -328,6 +328,17 @@ function optionalPositiveInteger(value, label) {
|
|
|
328
328
|
throw new Error(`${label} must be a positive integer`);
|
|
329
329
|
return value;
|
|
330
330
|
}
|
|
331
|
+
function optionalStringArray(value, label) {
|
|
332
|
+
if (value === undefined)
|
|
333
|
+
return;
|
|
334
|
+
if (!Array.isArray(value))
|
|
335
|
+
throw new Error(`${label} must be an array`);
|
|
336
|
+
const values = value.map((entry, index) => {
|
|
337
|
+
assertString(entry, `${label}[${index}]`);
|
|
338
|
+
return entry.trim();
|
|
339
|
+
}).filter(Boolean);
|
|
340
|
+
return values.length ? values : undefined;
|
|
341
|
+
}
|
|
331
342
|
function normalizeGoalSpec(value, label = "goal") {
|
|
332
343
|
if (value === undefined)
|
|
333
344
|
return;
|
|
@@ -399,6 +410,14 @@ function validateTarget(value, label) {
|
|
|
399
410
|
throw new Error(`${label}.sandbox is currently supported only for provider codewith, codex, or cursor`);
|
|
400
411
|
}
|
|
401
412
|
}
|
|
413
|
+
if (value.allowlist !== undefined) {
|
|
414
|
+
assertObject(value.allowlist, `${label}.allowlist`);
|
|
415
|
+
optionalStringArray(value.allowlist.tools, `${label}.allowlist.tools`);
|
|
416
|
+
optionalStringArray(value.allowlist.commands, `${label}.allowlist.commands`);
|
|
417
|
+
if (value.allowlist.enforcement !== undefined && value.allowlist.enforcement !== "metadata_only") {
|
|
418
|
+
throw new Error(`${label}.allowlist.enforcement must be metadata_only`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
402
421
|
return value;
|
|
403
422
|
}
|
|
404
423
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -1033,6 +1052,30 @@ class Store {
|
|
|
1033
1052
|
throw new Error(`loop not found after update: ${id}`);
|
|
1034
1053
|
return after;
|
|
1035
1054
|
}
|
|
1055
|
+
renameLoop(id, name, opts = {}) {
|
|
1056
|
+
const current = this.getLoop(id);
|
|
1057
|
+
if (!current)
|
|
1058
|
+
throw new Error(`loop not found: ${id}`);
|
|
1059
|
+
const trimmed = name.trim();
|
|
1060
|
+
if (!trimmed)
|
|
1061
|
+
throw new Error("loop name must not be empty");
|
|
1062
|
+
const updated = (opts.now ?? new Date).toISOString();
|
|
1063
|
+
this.db.query(`UPDATE loops SET name=$name, updated_at=$updated
|
|
1064
|
+
WHERE id=$id
|
|
1065
|
+
AND ($daemonLeaseId IS NULL OR EXISTS (
|
|
1066
|
+
SELECT 1 FROM daemon_lease WHERE id=$daemonLeaseId AND expires_at > $now
|
|
1067
|
+
))`).run({
|
|
1068
|
+
$id: id,
|
|
1069
|
+
$name: trimmed,
|
|
1070
|
+
$updated: updated,
|
|
1071
|
+
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
1072
|
+
$now: updated
|
|
1073
|
+
});
|
|
1074
|
+
const after = this.getLoop(id);
|
|
1075
|
+
if (!after)
|
|
1076
|
+
throw new Error(`loop not found after rename: ${id}`);
|
|
1077
|
+
return after;
|
|
1078
|
+
}
|
|
1036
1079
|
archiveLoop(idOrName) {
|
|
1037
1080
|
const loop = this.requireLoop(idOrName);
|
|
1038
1081
|
if (loop.archivedAt)
|
|
@@ -2465,6 +2508,16 @@ function metadataEnv(metadata) {
|
|
|
2465
2508
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
2466
2509
|
return env;
|
|
2467
2510
|
}
|
|
2511
|
+
function allowlistEnv(allowlist) {
|
|
2512
|
+
const env = {};
|
|
2513
|
+
if (allowlist?.tools?.length)
|
|
2514
|
+
env.LOOPS_AGENT_ALLOWED_TOOLS = allowlist.tools.join(",");
|
|
2515
|
+
if (allowlist?.commands?.length)
|
|
2516
|
+
env.LOOPS_AGENT_ALLOWED_COMMANDS = allowlist.commands.join(",");
|
|
2517
|
+
if (allowlist?.tools?.length || allowlist?.commands?.length)
|
|
2518
|
+
env.LOOPS_AGENT_ALLOWLIST_ENFORCEMENT = "metadata_only";
|
|
2519
|
+
return env;
|
|
2520
|
+
}
|
|
2468
2521
|
function providerCommand(provider) {
|
|
2469
2522
|
switch (provider) {
|
|
2470
2523
|
case "claude":
|
|
@@ -2672,7 +2725,8 @@ function commandSpec(target) {
|
|
|
2672
2725
|
account: agentTarget.account,
|
|
2673
2726
|
accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
|
|
2674
2727
|
preflightAnyOf: agentTarget.provider === "cursor" ? ["cursor", "agent"] : undefined,
|
|
2675
|
-
stdin: agentTarget.prompt
|
|
2728
|
+
stdin: agentTarget.prompt,
|
|
2729
|
+
allowlist: agentTarget.allowlist
|
|
2676
2730
|
};
|
|
2677
2731
|
}
|
|
2678
2732
|
function executionEnv(spec, metadata, opts) {
|
|
@@ -2684,6 +2738,7 @@ function executionEnv(spec, metadata, opts) {
|
|
|
2684
2738
|
Object.assign(env, accountEnv);
|
|
2685
2739
|
}
|
|
2686
2740
|
Object.assign(env, spec.env ?? {});
|
|
2741
|
+
Object.assign(env, allowlistEnv(spec.allowlist));
|
|
2687
2742
|
env.PATH = normalizeExecutionPath(env);
|
|
2688
2743
|
Object.assign(env, metadataEnv(metadata));
|
|
2689
2744
|
return env;
|
|
@@ -2722,6 +2777,9 @@ function remoteBootstrapLines(spec, metadata) {
|
|
|
2722
2777
|
continue;
|
|
2723
2778
|
lines.push(`export ${key}=${shellQuote(value)}`);
|
|
2724
2779
|
}
|
|
2780
|
+
for (const [key, value] of Object.entries(allowlistEnv(spec.allowlist))) {
|
|
2781
|
+
lines.push(`export ${key}=${shellQuote(value)}`);
|
|
2782
|
+
}
|
|
2725
2783
|
return lines;
|
|
2726
2784
|
}
|
|
2727
2785
|
function remoteScript(spec, metadata) {
|
|
@@ -4361,7 +4419,7 @@ function enableStartup(result) {
|
|
|
4361
4419
|
// package.json
|
|
4362
4420
|
var package_default = {
|
|
4363
4421
|
name: "@hasna/loops",
|
|
4364
|
-
version: "0.3.
|
|
4422
|
+
version: "0.3.17",
|
|
4365
4423
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
4366
4424
|
type: "module",
|
|
4367
4425
|
main: "dist/index.js",
|
package/dist/index.d.ts
CHANGED
|
@@ -8,8 +8,10 @@ export { listOpenMachines, refreshLoopMachine, resolveLoopMachine } from "./lib/
|
|
|
8
8
|
export { tick } from "./lib/scheduler.js";
|
|
9
9
|
export { executeWorkflow, executeLoopTarget, preflightWorkflow } from "./lib/workflow-runner.js";
|
|
10
10
|
export { workflowExecutionOrder, workflowBodyFromJson } from "./lib/workflow-spec.js";
|
|
11
|
-
export { EVENT_WORKER_VERIFIER_TEMPLATE_ID, TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID, getLoopTemplate, listLoopTemplates, renderEventWorkerVerifierWorkflow, renderLoopTemplate, renderTodosTaskWorkerVerifierWorkflow, } from "./lib/templates.js";
|
|
11
|
+
export { BOUNDED_AGENT_WORKER_VERIFIER_TEMPLATE_ID, EVENT_WORKER_VERIFIER_TEMPLATE_ID, TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID, getLoopTemplate, listLoopTemplates, renderBoundedAgentWorkerVerifierWorkflow, renderEventWorkerVerifierWorkflow, renderLoopTemplate, renderTodosTaskWorkerVerifierWorkflow, } from "./lib/templates.js";
|
|
12
12
|
export { runDoctor } from "./lib/doctor.js";
|
|
13
|
+
export { buildHealthReport, classifyRunFailure, expectationForLoop } from "./lib/health.js";
|
|
14
|
+
export { buildDuplicateOverlapReport, buildNameHygieneReport, buildScriptInventoryReport } from "./lib/hygiene.js";
|
|
13
15
|
export { runGoal } from "./lib/goal/runner.js";
|
|
14
16
|
export { resolveGoalModel } from "./lib/goal/model-factory.js";
|
|
15
17
|
export { isTerminal as isGoalTerminal, readyNodeKeys, rollupSummary } from "./lib/goal/status.js";
|