@hasna/loops 0.4.2 → 0.4.4
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/CHANGELOG.md +76 -0
- package/README.md +6 -1
- package/dist/api/index.js +2 -2
- package/dist/cli/index.js +926 -416
- package/dist/daemon/index.js +186 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +757 -68
- package/dist/lib/migration.d.ts +109 -0
- package/dist/lib/mode.js +2 -2
- package/dist/lib/route/todos-cli.d.ts +0 -1
- package/dist/lib/route/types.d.ts +0 -10
- package/dist/lib/storage/index.js +184 -0
- package/dist/lib/storage/sqlite.js +184 -0
- package/dist/lib/store.d.ts +41 -0
- package/dist/lib/store.js +184 -0
- package/dist/lib/template-kit.d.ts +8 -10
- package/dist/lib/templates.d.ts +0 -1
- package/dist/mcp/index.js +186 -2
- package/dist/runner/index.js +2 -2
- package/dist/sdk/index.d.ts +9 -0
- package/dist/sdk/index.js +981 -1
- package/docs/DEPLOYMENT_MODES.md +73 -5
- package/docs/USAGE.md +52 -1
- package/package.json +2 -2
package/dist/daemon/index.js
CHANGED
|
@@ -3843,6 +3843,190 @@ class Store {
|
|
|
3843
3843
|
const row = status ? this.db.query("SELECT COUNT(*) AS count FROM loop_runs WHERE status = ?").get(status) : this.db.query("SELECT COUNT(*) AS count FROM loop_runs").get();
|
|
3844
3844
|
return row?.count ?? 0;
|
|
3845
3845
|
}
|
|
3846
|
+
exportMigrationRows(opts = {}) {
|
|
3847
|
+
const includeRuns = opts.includeRuns ?? true;
|
|
3848
|
+
const workflows = this.db.query("SELECT * FROM workflow_specs ORDER BY created_at ASC, id ASC").all().map(rowToWorkflow);
|
|
3849
|
+
const loops = this.db.query("SELECT * FROM loops ORDER BY created_at ASC, id ASC").all().map(rowToLoop);
|
|
3850
|
+
const runs = includeRuns ? this.db.query("SELECT * FROM loop_runs ORDER BY created_at ASC, id ASC").all().map(rowToRun) : [];
|
|
3851
|
+
return { schemaVersion: SCHEMA_USER_VERSION, workflows, loops, runs, checks: this.migrationChecks() };
|
|
3852
|
+
}
|
|
3853
|
+
countTable(table) {
|
|
3854
|
+
const row = this.db.query(`SELECT COUNT(*) AS count FROM ${table}`).get();
|
|
3855
|
+
return row?.count ?? 0;
|
|
3856
|
+
}
|
|
3857
|
+
migrationChecks() {
|
|
3858
|
+
const now = nowIso();
|
|
3859
|
+
return {
|
|
3860
|
+
unsupportedCounts: {
|
|
3861
|
+
workflowInvocations: this.countTable("workflow_invocations"),
|
|
3862
|
+
workflowWorkItems: this.countTable("workflow_work_items"),
|
|
3863
|
+
workflowRuns: this.countTable("workflow_runs"),
|
|
3864
|
+
workflowStepRuns: this.countTable("workflow_step_runs"),
|
|
3865
|
+
workflowEvents: this.countTable("workflow_events"),
|
|
3866
|
+
goals: this.countTable("goals"),
|
|
3867
|
+
goalPlanNodes: this.countTable("goal_plan_nodes"),
|
|
3868
|
+
goalRuns: this.countTable("goal_runs")
|
|
3869
|
+
},
|
|
3870
|
+
volatileCounts: {
|
|
3871
|
+
daemonLeases: this.countTable("daemon_lease"),
|
|
3872
|
+
activeDaemonLeases: this.db.query("SELECT COUNT(*) AS count FROM daemon_lease WHERE expires_at > ?").get(now)?.count ?? 0,
|
|
3873
|
+
runningLoopRuns: this.db.query("SELECT COUNT(*) AS count FROM loop_runs WHERE status = 'running'").get()?.count ?? 0,
|
|
3874
|
+
runningWorkflowRuns: this.db.query("SELECT COUNT(*) AS count FROM workflow_runs WHERE status = 'running'").get()?.count ?? 0,
|
|
3875
|
+
runningWorkflowStepRuns: this.db.query("SELECT COUNT(*) AS count FROM workflow_step_runs WHERE status = 'running'").get()?.count ?? 0,
|
|
3876
|
+
leasedWorkflowWorkItems: this.db.query("SELECT COUNT(*) AS count FROM workflow_work_items WHERE lease_expires_at IS NOT NULL OR status IN ('admitted', 'running')").get()?.count ?? 0
|
|
3877
|
+
}
|
|
3878
|
+
};
|
|
3879
|
+
}
|
|
3880
|
+
upsertMigrationWorkflow(workflow, opts = {}) {
|
|
3881
|
+
const existing = this.getWorkflow(workflow.id);
|
|
3882
|
+
if (existing && !opts.replace)
|
|
3883
|
+
return existing;
|
|
3884
|
+
this.db.query(`INSERT INTO workflow_specs (id, name, description, version, status, goal_json, steps_json, created_at, updated_at)
|
|
3885
|
+
VALUES ($id, $name, $description, $version, $status, $goal, $steps, $created, $updated)
|
|
3886
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3887
|
+
name=$name,
|
|
3888
|
+
description=$description,
|
|
3889
|
+
version=$version,
|
|
3890
|
+
status=$status,
|
|
3891
|
+
goal_json=$goal,
|
|
3892
|
+
steps_json=$steps,
|
|
3893
|
+
created_at=$created,
|
|
3894
|
+
updated_at=$updated`).run({
|
|
3895
|
+
$id: workflow.id,
|
|
3896
|
+
$name: workflow.name,
|
|
3897
|
+
$description: workflow.description ?? null,
|
|
3898
|
+
$version: workflow.version,
|
|
3899
|
+
$status: workflow.status,
|
|
3900
|
+
$goal: workflow.goal ? JSON.stringify(workflow.goal) : null,
|
|
3901
|
+
$steps: JSON.stringify(workflow.steps),
|
|
3902
|
+
$created: workflow.createdAt,
|
|
3903
|
+
$updated: workflow.updatedAt
|
|
3904
|
+
});
|
|
3905
|
+
const imported = this.getWorkflow(workflow.id);
|
|
3906
|
+
if (!imported)
|
|
3907
|
+
throw new Error(`workflow not found after migration import: ${workflow.id}`);
|
|
3908
|
+
return imported;
|
|
3909
|
+
}
|
|
3910
|
+
upsertMigrationLoop(loop, opts = {}) {
|
|
3911
|
+
const existing = this.getLoop(loop.id);
|
|
3912
|
+
if (existing && !opts.replace)
|
|
3913
|
+
return existing;
|
|
3914
|
+
this.assertNoNestedWorkflowGoal(loop.target, loop.goal);
|
|
3915
|
+
this.db.query(`INSERT INTO loops (id, name, description, status, archived_at, archived_from_status, schedule_json, target_json,
|
|
3916
|
+
goal_json, machine_json, next_run_at, retry_scheduled_for, catch_up, catch_up_limit, overlap, max_attempts,
|
|
3917
|
+
retry_delay_ms, lease_ms, expires_at, created_at, updated_at)
|
|
3918
|
+
VALUES ($id, $name, $description, $status, $archivedAt, $archivedFromStatus, $schedule, $target,
|
|
3919
|
+
$goal, $machine, $nextRun, $retrySlot, $catchUp, $catchUpLimit, $overlap, $maxAttempts,
|
|
3920
|
+
$retryDelay, $leaseMs, $expiresAt, $created, $updated)
|
|
3921
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3922
|
+
name=$name,
|
|
3923
|
+
description=$description,
|
|
3924
|
+
status=$status,
|
|
3925
|
+
archived_at=$archivedAt,
|
|
3926
|
+
archived_from_status=$archivedFromStatus,
|
|
3927
|
+
schedule_json=$schedule,
|
|
3928
|
+
target_json=$target,
|
|
3929
|
+
goal_json=$goal,
|
|
3930
|
+
machine_json=$machine,
|
|
3931
|
+
next_run_at=$nextRun,
|
|
3932
|
+
retry_scheduled_for=$retrySlot,
|
|
3933
|
+
catch_up=$catchUp,
|
|
3934
|
+
catch_up_limit=$catchUpLimit,
|
|
3935
|
+
overlap=$overlap,
|
|
3936
|
+
max_attempts=$maxAttempts,
|
|
3937
|
+
retry_delay_ms=$retryDelay,
|
|
3938
|
+
lease_ms=$leaseMs,
|
|
3939
|
+
expires_at=$expiresAt,
|
|
3940
|
+
created_at=$created,
|
|
3941
|
+
updated_at=$updated`).run({
|
|
3942
|
+
$id: loop.id,
|
|
3943
|
+
$name: loop.name,
|
|
3944
|
+
$description: loop.description ?? null,
|
|
3945
|
+
$status: loop.status,
|
|
3946
|
+
$archivedAt: loop.archivedAt ?? null,
|
|
3947
|
+
$archivedFromStatus: loop.archivedFromStatus ?? null,
|
|
3948
|
+
$schedule: JSON.stringify(loop.schedule),
|
|
3949
|
+
$target: JSON.stringify(loop.target),
|
|
3950
|
+
$goal: loop.goal ? JSON.stringify(loop.goal) : null,
|
|
3951
|
+
$machine: loop.machine ? JSON.stringify(loop.machine) : null,
|
|
3952
|
+
$nextRun: loop.nextRunAt ?? null,
|
|
3953
|
+
$retrySlot: loop.retryScheduledFor ?? null,
|
|
3954
|
+
$catchUp: loop.catchUp,
|
|
3955
|
+
$catchUpLimit: loop.catchUpLimit,
|
|
3956
|
+
$overlap: loop.overlap,
|
|
3957
|
+
$maxAttempts: loop.maxAttempts,
|
|
3958
|
+
$retryDelay: loop.retryDelayMs,
|
|
3959
|
+
$leaseMs: loop.leaseMs,
|
|
3960
|
+
$expiresAt: loop.expiresAt ?? null,
|
|
3961
|
+
$created: loop.createdAt,
|
|
3962
|
+
$updated: loop.updatedAt
|
|
3963
|
+
});
|
|
3964
|
+
const imported = this.getLoop(loop.id);
|
|
3965
|
+
if (!imported)
|
|
3966
|
+
throw new Error(`loop not found after migration import: ${loop.id}`);
|
|
3967
|
+
return imported;
|
|
3968
|
+
}
|
|
3969
|
+
upsertMigrationRun(run, opts = {}) {
|
|
3970
|
+
if (run.status === "running")
|
|
3971
|
+
throw new ValidationError(`cannot import running run ${run.id}`);
|
|
3972
|
+
const existing = this.getRun(run.id);
|
|
3973
|
+
if (existing && !opts.replace)
|
|
3974
|
+
return existing;
|
|
3975
|
+
this.db.query(`INSERT INTO loop_runs (id, loop_id, loop_name, scheduled_for, attempt, status, started_at, finished_at,
|
|
3976
|
+
claimed_by, claim_token, lease_expires_at, pid, pgid, process_started_at, exit_code, duration_ms,
|
|
3977
|
+
stdout, stderr, error, goal_run_id, created_at, updated_at)
|
|
3978
|
+
VALUES ($id, $loopId, $loopName, $scheduledFor, $attempt, $status, $startedAt, $finishedAt,
|
|
3979
|
+
$claimedBy, NULL, $leaseExpiresAt, $pid, $pgid, $processStartedAt, $exitCode, $durationMs,
|
|
3980
|
+
$stdout, $stderr, $error, $goalRunId, $created, $updated)
|
|
3981
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
3982
|
+
loop_id=$loopId,
|
|
3983
|
+
loop_name=$loopName,
|
|
3984
|
+
scheduled_for=$scheduledFor,
|
|
3985
|
+
attempt=$attempt,
|
|
3986
|
+
status=$status,
|
|
3987
|
+
started_at=$startedAt,
|
|
3988
|
+
finished_at=$finishedAt,
|
|
3989
|
+
claimed_by=$claimedBy,
|
|
3990
|
+
claim_token=NULL,
|
|
3991
|
+
lease_expires_at=$leaseExpiresAt,
|
|
3992
|
+
pid=$pid,
|
|
3993
|
+
pgid=$pgid,
|
|
3994
|
+
process_started_at=$processStartedAt,
|
|
3995
|
+
exit_code=$exitCode,
|
|
3996
|
+
duration_ms=$durationMs,
|
|
3997
|
+
stdout=$stdout,
|
|
3998
|
+
stderr=$stderr,
|
|
3999
|
+
error=$error,
|
|
4000
|
+
goal_run_id=$goalRunId,
|
|
4001
|
+
created_at=$created,
|
|
4002
|
+
updated_at=$updated`).run({
|
|
4003
|
+
$id: run.id,
|
|
4004
|
+
$loopId: run.loopId,
|
|
4005
|
+
$loopName: run.loopName,
|
|
4006
|
+
$scheduledFor: run.scheduledFor,
|
|
4007
|
+
$attempt: run.attempt,
|
|
4008
|
+
$status: run.status,
|
|
4009
|
+
$startedAt: run.startedAt ?? null,
|
|
4010
|
+
$finishedAt: run.finishedAt ?? null,
|
|
4011
|
+
$claimedBy: run.claimedBy ?? null,
|
|
4012
|
+
$leaseExpiresAt: run.leaseExpiresAt ?? null,
|
|
4013
|
+
$pid: run.pid ?? null,
|
|
4014
|
+
$pgid: run.pgid ?? null,
|
|
4015
|
+
$processStartedAt: run.processStartedAt ?? null,
|
|
4016
|
+
$exitCode: run.exitCode ?? null,
|
|
4017
|
+
$durationMs: run.durationMs ?? null,
|
|
4018
|
+
$stdout: scrubbedOrNull(run.stdout),
|
|
4019
|
+
$stderr: scrubbedOrNull(run.stderr),
|
|
4020
|
+
$error: scrubbedOrNull(run.error),
|
|
4021
|
+
$goalRunId: run.goalRunId ?? null,
|
|
4022
|
+
$created: run.createdAt,
|
|
4023
|
+
$updated: run.updatedAt
|
|
4024
|
+
});
|
|
4025
|
+
const imported = this.getRun(run.id);
|
|
4026
|
+
if (!imported)
|
|
4027
|
+
throw new Error(`run not found after migration import: ${run.id}`);
|
|
4028
|
+
return imported;
|
|
4029
|
+
}
|
|
3846
4030
|
pruneHistory(opts) {
|
|
3847
4031
|
const { maxAgeDays, keepPerLoop } = opts;
|
|
3848
4032
|
if (maxAgeDays === undefined && keepPerLoop === undefined) {
|
|
@@ -7623,7 +7807,7 @@ function enableStartup(result) {
|
|
|
7623
7807
|
// package.json
|
|
7624
7808
|
var package_default = {
|
|
7625
7809
|
name: "@hasna/loops",
|
|
7626
|
-
version: "0.4.
|
|
7810
|
+
version: "0.4.4",
|
|
7627
7811
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
7628
7812
|
type: "module",
|
|
7629
7813
|
main: "dist/index.js",
|
|
@@ -7697,7 +7881,7 @@ var package_default = {
|
|
|
7697
7881
|
prepare: "test -d dist || bun run build",
|
|
7698
7882
|
"dev:cli": "bun run src/cli/index.ts",
|
|
7699
7883
|
"dev:daemon": "bun run src/daemon/index.ts",
|
|
7700
|
-
prepublishOnly: "bun run build && bun run test:boundary"
|
|
7884
|
+
prepublishOnly: "bun run build && bun run typecheck && bun test && bun run test:boundary"
|
|
7701
7885
|
},
|
|
7702
7886
|
keywords: [
|
|
7703
7887
|
"loops",
|
package/dist/index.d.ts
CHANGED
|
@@ -30,7 +30,9 @@ export { workflowBodyFromJson, workflowExecutionOrder } from "./lib/workflow-spe
|
|
|
30
30
|
export { listOpenMachines, refreshLoopMachine, resolveLoopMachine } from "./lib/machines.js";
|
|
31
31
|
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";
|
|
32
32
|
export { buildDuplicateOverlapReport, buildNameHygieneReport, buildScriptInventoryReport } from "./lib/hygiene.js";
|
|
33
|
+
export { LOOPS_MIGRATION_SCHEMA, applyImportMigrationBundle, buildImportMigrationPlan, buildSelfHostedMigrationPlan, exportLoopsMigrationBundle, migrationHash, publicMigrationBundle, registerSelfHostedRunner, selfHostedControlPlaneSummary, validateLoopsMigrationBundle, } from "./lib/migration.js";
|
|
33
34
|
export { runGoal } from "./lib/goal/runner.js";
|
|
34
35
|
export { resolveGoalModel } from "./lib/goal/model-factory.js";
|
|
35
36
|
export { isTerminal as isGoalTerminal, readyNodeKeys, rollupSummary } from "./lib/goal/status.js";
|
|
36
37
|
export type { CreateWorkflowInvocationInput, Goal, GoalAutoExecute, GoalExecutorResult, GoalPlan, GoalPlanNode, GoalPlanNodeStatus, GoalPlanStatus, GoalRollup, GoalRun, GoalSpec, GoalStatus, LoopMachineConfidence, LoopMachineRef, LoopMachineRoute, LoopTemplateKind, LoopTemplateSource, LoopTemplateSummary, LoopTemplateVariable, LoopTemplateVariableType, OpenAutomationsRuntimeBinding, PersistGuardOptions, UpsertWorkflowWorkItemInput, WorkflowInvocation, WorkflowInvocationIntent, WorkflowInvocationOutputPolicy, WorkflowInvocationRef, WorkflowInvocationScope, WorkflowInvocationSourceKind, WorkflowInvocationSubjectKind, WorkflowWorkItem, WorkflowWorkItemStatus, } from "./types.js";
|
|
38
|
+
export type { ApplyLoopsMigrationResult, ExportLoopsMigrationOptions, ImportLoopsMigrationOptions, LoopsMigrationAction, LoopsMigrationBundle, LoopsMigrationPlan, LoopsMigrationPlanRow, LoopsMigrationPlanSummary, LoopsMigrationResource, RunnerRegistrationOptions, RunnerRegistrationResult, SelfHostedPlanOptions, } from "./lib/migration.js";
|