@hasna/loops 0.4.9 → 0.4.11
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 +45 -0
- package/dist/api/index.js +1 -1
- package/dist/cli/index.js +265 -44
- package/dist/daemon/daemon.d.ts +18 -0
- package/dist/daemon/index.js +99 -24
- package/dist/index.js +65 -15
- package/dist/lib/mode.js +1 -1
- package/dist/lib/route/pr-review.d.ts +24 -1
- package/dist/lib/scheduler.d.ts +13 -0
- package/dist/lib/storage/index.js +30 -11
- package/dist/lib/storage/sqlite.js +30 -11
- package/dist/lib/store.js +30 -11
- package/dist/mcp/index.js +65 -15
- package/dist/runner/index.js +22 -4
- package/dist/sdk/index.js +65 -15
- package/package.json +1 -1
|
@@ -54,8 +54,12 @@ function nowIso() {
|
|
|
54
54
|
import { mkdirSync } from "fs";
|
|
55
55
|
import { homedir } from "os";
|
|
56
56
|
import { join } from "path";
|
|
57
|
+
function homeDir() {
|
|
58
|
+
const home = process.env.HOME?.trim();
|
|
59
|
+
return home ? home : homedir();
|
|
60
|
+
}
|
|
57
61
|
function dataDir() {
|
|
58
|
-
return process.env.LOOPS_DATA_DIR || join(
|
|
62
|
+
return process.env.LOOPS_DATA_DIR || join(homeDir(), ".hasna", "loops");
|
|
59
63
|
}
|
|
60
64
|
function ensureDataDir() {
|
|
61
65
|
const dir = dataDir();
|
|
@@ -72,10 +76,10 @@ function daemonLogPath() {
|
|
|
72
76
|
return join(dataDir(), "daemon.log");
|
|
73
77
|
}
|
|
74
78
|
function systemdServicePath() {
|
|
75
|
-
return join(
|
|
79
|
+
return join(homeDir(), ".config", "systemd", "user", "loops-daemon.service");
|
|
76
80
|
}
|
|
77
81
|
function launchdPlistPath() {
|
|
78
|
-
return join(
|
|
82
|
+
return join(homeDir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// src/lib/process-identity.ts
|
|
@@ -1472,6 +1476,21 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
|
|
|
1472
1476
|
function scrubbedOrNull(value) {
|
|
1473
1477
|
return value == null ? null : scrubSecrets(value);
|
|
1474
1478
|
}
|
|
1479
|
+
var MAX_PERSISTED_RUN_OUTPUT_CHARS = 64 * 1024;
|
|
1480
|
+
function clampPersistedRunOutput(value) {
|
|
1481
|
+
if (value == null || value.length <= MAX_PERSISTED_RUN_OUTPUT_CHARS)
|
|
1482
|
+
return value;
|
|
1483
|
+
const half = Math.floor(MAX_PERSISTED_RUN_OUTPUT_CHARS / 2);
|
|
1484
|
+
const head = value.slice(0, half);
|
|
1485
|
+
const tail = value.slice(value.length - half);
|
|
1486
|
+
const omitted = value.length - head.length - tail.length;
|
|
1487
|
+
return `${head}
|
|
1488
|
+
\u2026[${omitted} chars truncated by loops run-output retention]\u2026
|
|
1489
|
+
${tail}`;
|
|
1490
|
+
}
|
|
1491
|
+
function persistedRunOutput(value) {
|
|
1492
|
+
return clampPersistedRunOutput(scrubbedOrNull(value));
|
|
1493
|
+
}
|
|
1475
1494
|
function chmodIfExists(path, mode) {
|
|
1476
1495
|
try {
|
|
1477
1496
|
if (existsSync(path))
|
|
@@ -3218,8 +3237,8 @@ class Store {
|
|
|
3218
3237
|
))`).run({
|
|
3219
3238
|
$workflowRunId: workflowRunId,
|
|
3220
3239
|
$stepId: stepId,
|
|
3221
|
-
$stdout: progress.stdout === undefined ? null :
|
|
3222
|
-
$stderr: progress.stderr === undefined ? null :
|
|
3240
|
+
$stdout: progress.stdout === undefined ? null : persistedRunOutput(progress.stdout),
|
|
3241
|
+
$stderr: progress.stderr === undefined ? null : persistedRunOutput(progress.stderr),
|
|
3223
3242
|
$updated: now,
|
|
3224
3243
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
3225
3244
|
$now: now
|
|
@@ -3280,8 +3299,8 @@ class Store {
|
|
|
3280
3299
|
$finished: finishedAt,
|
|
3281
3300
|
$exitCode: patch.exitCode ?? null,
|
|
3282
3301
|
$durationMs: patch.durationMs ?? null,
|
|
3283
|
-
$stdout:
|
|
3284
|
-
$stderr:
|
|
3302
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3303
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3285
3304
|
$error: error ?? null,
|
|
3286
3305
|
$updated: finishedAt,
|
|
3287
3306
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
@@ -3659,8 +3678,8 @@ class Store {
|
|
|
3659
3678
|
$pid: patch.pid ?? null,
|
|
3660
3679
|
$exitCode: patch.exitCode ?? null,
|
|
3661
3680
|
$durationMs: patch.durationMs ?? null,
|
|
3662
|
-
$stdout:
|
|
3663
|
-
$stderr:
|
|
3681
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3682
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3664
3683
|
$error: error ?? null,
|
|
3665
3684
|
$updated: finishedAt,
|
|
3666
3685
|
$claimedBy: opts.claimedBy ?? null,
|
|
@@ -4057,8 +4076,8 @@ class Store {
|
|
|
4057
4076
|
$processStartedAt: run.processStartedAt ?? null,
|
|
4058
4077
|
$exitCode: run.exitCode ?? null,
|
|
4059
4078
|
$durationMs: run.durationMs ?? null,
|
|
4060
|
-
$stdout:
|
|
4061
|
-
$stderr:
|
|
4079
|
+
$stdout: persistedRunOutput(run.stdout),
|
|
4080
|
+
$stderr: persistedRunOutput(run.stderr),
|
|
4062
4081
|
$error: scrubbedOrNull(run.error),
|
|
4063
4082
|
$goalRunId: run.goalRunId ?? null,
|
|
4064
4083
|
$created: run.createdAt,
|
|
@@ -54,8 +54,12 @@ function nowIso() {
|
|
|
54
54
|
import { mkdirSync } from "fs";
|
|
55
55
|
import { homedir } from "os";
|
|
56
56
|
import { join } from "path";
|
|
57
|
+
function homeDir() {
|
|
58
|
+
const home = process.env.HOME?.trim();
|
|
59
|
+
return home ? home : homedir();
|
|
60
|
+
}
|
|
57
61
|
function dataDir() {
|
|
58
|
-
return process.env.LOOPS_DATA_DIR || join(
|
|
62
|
+
return process.env.LOOPS_DATA_DIR || join(homeDir(), ".hasna", "loops");
|
|
59
63
|
}
|
|
60
64
|
function ensureDataDir() {
|
|
61
65
|
const dir = dataDir();
|
|
@@ -72,10 +76,10 @@ function daemonLogPath() {
|
|
|
72
76
|
return join(dataDir(), "daemon.log");
|
|
73
77
|
}
|
|
74
78
|
function systemdServicePath() {
|
|
75
|
-
return join(
|
|
79
|
+
return join(homeDir(), ".config", "systemd", "user", "loops-daemon.service");
|
|
76
80
|
}
|
|
77
81
|
function launchdPlistPath() {
|
|
78
|
-
return join(
|
|
82
|
+
return join(homeDir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// src/lib/process-identity.ts
|
|
@@ -1472,6 +1476,21 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
|
|
|
1472
1476
|
function scrubbedOrNull(value) {
|
|
1473
1477
|
return value == null ? null : scrubSecrets(value);
|
|
1474
1478
|
}
|
|
1479
|
+
var MAX_PERSISTED_RUN_OUTPUT_CHARS = 64 * 1024;
|
|
1480
|
+
function clampPersistedRunOutput(value) {
|
|
1481
|
+
if (value == null || value.length <= MAX_PERSISTED_RUN_OUTPUT_CHARS)
|
|
1482
|
+
return value;
|
|
1483
|
+
const half = Math.floor(MAX_PERSISTED_RUN_OUTPUT_CHARS / 2);
|
|
1484
|
+
const head = value.slice(0, half);
|
|
1485
|
+
const tail = value.slice(value.length - half);
|
|
1486
|
+
const omitted = value.length - head.length - tail.length;
|
|
1487
|
+
return `${head}
|
|
1488
|
+
\u2026[${omitted} chars truncated by loops run-output retention]\u2026
|
|
1489
|
+
${tail}`;
|
|
1490
|
+
}
|
|
1491
|
+
function persistedRunOutput(value) {
|
|
1492
|
+
return clampPersistedRunOutput(scrubbedOrNull(value));
|
|
1493
|
+
}
|
|
1475
1494
|
function chmodIfExists(path, mode) {
|
|
1476
1495
|
try {
|
|
1477
1496
|
if (existsSync(path))
|
|
@@ -3218,8 +3237,8 @@ class Store {
|
|
|
3218
3237
|
))`).run({
|
|
3219
3238
|
$workflowRunId: workflowRunId,
|
|
3220
3239
|
$stepId: stepId,
|
|
3221
|
-
$stdout: progress.stdout === undefined ? null :
|
|
3222
|
-
$stderr: progress.stderr === undefined ? null :
|
|
3240
|
+
$stdout: progress.stdout === undefined ? null : persistedRunOutput(progress.stdout),
|
|
3241
|
+
$stderr: progress.stderr === undefined ? null : persistedRunOutput(progress.stderr),
|
|
3223
3242
|
$updated: now,
|
|
3224
3243
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
3225
3244
|
$now: now
|
|
@@ -3280,8 +3299,8 @@ class Store {
|
|
|
3280
3299
|
$finished: finishedAt,
|
|
3281
3300
|
$exitCode: patch.exitCode ?? null,
|
|
3282
3301
|
$durationMs: patch.durationMs ?? null,
|
|
3283
|
-
$stdout:
|
|
3284
|
-
$stderr:
|
|
3302
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3303
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3285
3304
|
$error: error ?? null,
|
|
3286
3305
|
$updated: finishedAt,
|
|
3287
3306
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
@@ -3659,8 +3678,8 @@ class Store {
|
|
|
3659
3678
|
$pid: patch.pid ?? null,
|
|
3660
3679
|
$exitCode: patch.exitCode ?? null,
|
|
3661
3680
|
$durationMs: patch.durationMs ?? null,
|
|
3662
|
-
$stdout:
|
|
3663
|
-
$stderr:
|
|
3681
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3682
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3664
3683
|
$error: error ?? null,
|
|
3665
3684
|
$updated: finishedAt,
|
|
3666
3685
|
$claimedBy: opts.claimedBy ?? null,
|
|
@@ -4057,8 +4076,8 @@ class Store {
|
|
|
4057
4076
|
$processStartedAt: run.processStartedAt ?? null,
|
|
4058
4077
|
$exitCode: run.exitCode ?? null,
|
|
4059
4078
|
$durationMs: run.durationMs ?? null,
|
|
4060
|
-
$stdout:
|
|
4061
|
-
$stderr:
|
|
4079
|
+
$stdout: persistedRunOutput(run.stdout),
|
|
4080
|
+
$stderr: persistedRunOutput(run.stderr),
|
|
4062
4081
|
$error: scrubbedOrNull(run.error),
|
|
4063
4082
|
$goalRunId: run.goalRunId ?? null,
|
|
4064
4083
|
$created: run.createdAt,
|
package/dist/lib/store.js
CHANGED
|
@@ -54,8 +54,12 @@ function nowIso() {
|
|
|
54
54
|
import { mkdirSync } from "fs";
|
|
55
55
|
import { homedir } from "os";
|
|
56
56
|
import { join } from "path";
|
|
57
|
+
function homeDir() {
|
|
58
|
+
const home = process.env.HOME?.trim();
|
|
59
|
+
return home ? home : homedir();
|
|
60
|
+
}
|
|
57
61
|
function dataDir() {
|
|
58
|
-
return process.env.LOOPS_DATA_DIR || join(
|
|
62
|
+
return process.env.LOOPS_DATA_DIR || join(homeDir(), ".hasna", "loops");
|
|
59
63
|
}
|
|
60
64
|
function ensureDataDir() {
|
|
61
65
|
const dir = dataDir();
|
|
@@ -72,10 +76,10 @@ function daemonLogPath() {
|
|
|
72
76
|
return join(dataDir(), "daemon.log");
|
|
73
77
|
}
|
|
74
78
|
function systemdServicePath() {
|
|
75
|
-
return join(
|
|
79
|
+
return join(homeDir(), ".config", "systemd", "user", "loops-daemon.service");
|
|
76
80
|
}
|
|
77
81
|
function launchdPlistPath() {
|
|
78
|
-
return join(
|
|
82
|
+
return join(homeDir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// src/lib/process-identity.ts
|
|
@@ -1472,6 +1476,21 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
|
|
|
1472
1476
|
function scrubbedOrNull(value) {
|
|
1473
1477
|
return value == null ? null : scrubSecrets(value);
|
|
1474
1478
|
}
|
|
1479
|
+
var MAX_PERSISTED_RUN_OUTPUT_CHARS = 64 * 1024;
|
|
1480
|
+
function clampPersistedRunOutput(value) {
|
|
1481
|
+
if (value == null || value.length <= MAX_PERSISTED_RUN_OUTPUT_CHARS)
|
|
1482
|
+
return value;
|
|
1483
|
+
const half = Math.floor(MAX_PERSISTED_RUN_OUTPUT_CHARS / 2);
|
|
1484
|
+
const head = value.slice(0, half);
|
|
1485
|
+
const tail = value.slice(value.length - half);
|
|
1486
|
+
const omitted = value.length - head.length - tail.length;
|
|
1487
|
+
return `${head}
|
|
1488
|
+
\u2026[${omitted} chars truncated by loops run-output retention]\u2026
|
|
1489
|
+
${tail}`;
|
|
1490
|
+
}
|
|
1491
|
+
function persistedRunOutput(value) {
|
|
1492
|
+
return clampPersistedRunOutput(scrubbedOrNull(value));
|
|
1493
|
+
}
|
|
1475
1494
|
function chmodIfExists(path, mode) {
|
|
1476
1495
|
try {
|
|
1477
1496
|
if (existsSync(path))
|
|
@@ -3218,8 +3237,8 @@ class Store {
|
|
|
3218
3237
|
))`).run({
|
|
3219
3238
|
$workflowRunId: workflowRunId,
|
|
3220
3239
|
$stepId: stepId,
|
|
3221
|
-
$stdout: progress.stdout === undefined ? null :
|
|
3222
|
-
$stderr: progress.stderr === undefined ? null :
|
|
3240
|
+
$stdout: progress.stdout === undefined ? null : persistedRunOutput(progress.stdout),
|
|
3241
|
+
$stderr: progress.stderr === undefined ? null : persistedRunOutput(progress.stderr),
|
|
3223
3242
|
$updated: now,
|
|
3224
3243
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
3225
3244
|
$now: now
|
|
@@ -3280,8 +3299,8 @@ class Store {
|
|
|
3280
3299
|
$finished: finishedAt,
|
|
3281
3300
|
$exitCode: patch.exitCode ?? null,
|
|
3282
3301
|
$durationMs: patch.durationMs ?? null,
|
|
3283
|
-
$stdout:
|
|
3284
|
-
$stderr:
|
|
3302
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3303
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3285
3304
|
$error: error ?? null,
|
|
3286
3305
|
$updated: finishedAt,
|
|
3287
3306
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
@@ -3659,8 +3678,8 @@ class Store {
|
|
|
3659
3678
|
$pid: patch.pid ?? null,
|
|
3660
3679
|
$exitCode: patch.exitCode ?? null,
|
|
3661
3680
|
$durationMs: patch.durationMs ?? null,
|
|
3662
|
-
$stdout:
|
|
3663
|
-
$stderr:
|
|
3681
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3682
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3664
3683
|
$error: error ?? null,
|
|
3665
3684
|
$updated: finishedAt,
|
|
3666
3685
|
$claimedBy: opts.claimedBy ?? null,
|
|
@@ -4057,8 +4076,8 @@ class Store {
|
|
|
4057
4076
|
$processStartedAt: run.processStartedAt ?? null,
|
|
4058
4077
|
$exitCode: run.exitCode ?? null,
|
|
4059
4078
|
$durationMs: run.durationMs ?? null,
|
|
4060
|
-
$stdout:
|
|
4061
|
-
$stderr:
|
|
4079
|
+
$stdout: persistedRunOutput(run.stdout),
|
|
4080
|
+
$stderr: persistedRunOutput(run.stderr),
|
|
4062
4081
|
$error: scrubbedOrNull(run.error),
|
|
4063
4082
|
$goalRunId: run.goalRunId ?? null,
|
|
4064
4083
|
$created: run.createdAt,
|
package/dist/mcp/index.js
CHANGED
|
@@ -56,8 +56,12 @@ function nowIso() {
|
|
|
56
56
|
import { mkdirSync } from "fs";
|
|
57
57
|
import { homedir } from "os";
|
|
58
58
|
import { join } from "path";
|
|
59
|
+
function homeDir() {
|
|
60
|
+
const home = process.env.HOME?.trim();
|
|
61
|
+
return home ? home : homedir();
|
|
62
|
+
}
|
|
59
63
|
function dataDir() {
|
|
60
|
-
return process.env.LOOPS_DATA_DIR || join(
|
|
64
|
+
return process.env.LOOPS_DATA_DIR || join(homeDir(), ".hasna", "loops");
|
|
61
65
|
}
|
|
62
66
|
function ensureDataDir() {
|
|
63
67
|
const dir = dataDir();
|
|
@@ -74,10 +78,10 @@ function daemonLogPath() {
|
|
|
74
78
|
return join(dataDir(), "daemon.log");
|
|
75
79
|
}
|
|
76
80
|
function systemdServicePath() {
|
|
77
|
-
return join(
|
|
81
|
+
return join(homeDir(), ".config", "systemd", "user", "loops-daemon.service");
|
|
78
82
|
}
|
|
79
83
|
function launchdPlistPath() {
|
|
80
|
-
return join(
|
|
84
|
+
return join(homeDir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
// src/lib/process-identity.ts
|
|
@@ -1474,6 +1478,21 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
|
|
|
1474
1478
|
function scrubbedOrNull(value) {
|
|
1475
1479
|
return value == null ? null : scrubSecrets(value);
|
|
1476
1480
|
}
|
|
1481
|
+
var MAX_PERSISTED_RUN_OUTPUT_CHARS = 64 * 1024;
|
|
1482
|
+
function clampPersistedRunOutput(value) {
|
|
1483
|
+
if (value == null || value.length <= MAX_PERSISTED_RUN_OUTPUT_CHARS)
|
|
1484
|
+
return value;
|
|
1485
|
+
const half = Math.floor(MAX_PERSISTED_RUN_OUTPUT_CHARS / 2);
|
|
1486
|
+
const head = value.slice(0, half);
|
|
1487
|
+
const tail = value.slice(value.length - half);
|
|
1488
|
+
const omitted = value.length - head.length - tail.length;
|
|
1489
|
+
return `${head}
|
|
1490
|
+
\u2026[${omitted} chars truncated by loops run-output retention]\u2026
|
|
1491
|
+
${tail}`;
|
|
1492
|
+
}
|
|
1493
|
+
function persistedRunOutput(value) {
|
|
1494
|
+
return clampPersistedRunOutput(scrubbedOrNull(value));
|
|
1495
|
+
}
|
|
1477
1496
|
function chmodIfExists(path, mode) {
|
|
1478
1497
|
try {
|
|
1479
1498
|
if (existsSync(path))
|
|
@@ -3220,8 +3239,8 @@ class Store {
|
|
|
3220
3239
|
))`).run({
|
|
3221
3240
|
$workflowRunId: workflowRunId,
|
|
3222
3241
|
$stepId: stepId,
|
|
3223
|
-
$stdout: progress.stdout === undefined ? null :
|
|
3224
|
-
$stderr: progress.stderr === undefined ? null :
|
|
3242
|
+
$stdout: progress.stdout === undefined ? null : persistedRunOutput(progress.stdout),
|
|
3243
|
+
$stderr: progress.stderr === undefined ? null : persistedRunOutput(progress.stderr),
|
|
3225
3244
|
$updated: now,
|
|
3226
3245
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
3227
3246
|
$now: now
|
|
@@ -3282,8 +3301,8 @@ class Store {
|
|
|
3282
3301
|
$finished: finishedAt,
|
|
3283
3302
|
$exitCode: patch.exitCode ?? null,
|
|
3284
3303
|
$durationMs: patch.durationMs ?? null,
|
|
3285
|
-
$stdout:
|
|
3286
|
-
$stderr:
|
|
3304
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3305
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3287
3306
|
$error: error ?? null,
|
|
3288
3307
|
$updated: finishedAt,
|
|
3289
3308
|
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
@@ -3661,8 +3680,8 @@ class Store {
|
|
|
3661
3680
|
$pid: patch.pid ?? null,
|
|
3662
3681
|
$exitCode: patch.exitCode ?? null,
|
|
3663
3682
|
$durationMs: patch.durationMs ?? null,
|
|
3664
|
-
$stdout:
|
|
3665
|
-
$stderr:
|
|
3683
|
+
$stdout: persistedRunOutput(patch.stdout),
|
|
3684
|
+
$stderr: persistedRunOutput(patch.stderr),
|
|
3666
3685
|
$error: error ?? null,
|
|
3667
3686
|
$updated: finishedAt,
|
|
3668
3687
|
$claimedBy: opts.claimedBy ?? null,
|
|
@@ -4059,8 +4078,8 @@ class Store {
|
|
|
4059
4078
|
$processStartedAt: run.processStartedAt ?? null,
|
|
4060
4079
|
$exitCode: run.exitCode ?? null,
|
|
4061
4080
|
$durationMs: run.durationMs ?? null,
|
|
4062
|
-
$stdout:
|
|
4063
|
-
$stderr:
|
|
4081
|
+
$stdout: persistedRunOutput(run.stdout),
|
|
4082
|
+
$stderr: persistedRunOutput(run.stderr),
|
|
4064
4083
|
$error: scrubbedOrNull(run.error),
|
|
4065
4084
|
$goalRunId: run.goalRunId ?? null,
|
|
4066
4085
|
$created: run.createdAt,
|
|
@@ -4780,6 +4799,20 @@ function notifySpawn(pid, opts) {
|
|
|
4780
4799
|
function shellQuote(value) {
|
|
4781
4800
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
4782
4801
|
}
|
|
4802
|
+
function codewithProfileCandidateFromLine(line) {
|
|
4803
|
+
const cols = line.trim().split(/\s+/);
|
|
4804
|
+
if (cols[0] === "*")
|
|
4805
|
+
return cols[1];
|
|
4806
|
+
return cols[0];
|
|
4807
|
+
}
|
|
4808
|
+
function codewithProfileForError(profile) {
|
|
4809
|
+
return /[\u0000-\u001F\u007F]/.test(profile) ? JSON.stringify(profile) ?? profile : profile;
|
|
4810
|
+
}
|
|
4811
|
+
function assertCodewithAuthProfileSupported(profile) {
|
|
4812
|
+
if (profile.includes("\x00")) {
|
|
4813
|
+
throw new Error(`codewith auth profile contains unsupported NUL byte: ${codewithProfileForError(profile)}`);
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4783
4816
|
function metadataEnv(metadata) {
|
|
4784
4817
|
const env = {};
|
|
4785
4818
|
if (metadata.loopId)
|
|
@@ -4846,6 +4879,9 @@ function commandSpec(target, opts) {
|
|
|
4846
4879
|
};
|
|
4847
4880
|
}
|
|
4848
4881
|
const agentTarget = target;
|
|
4882
|
+
if (agentTarget.provider === "codewith" && agentTarget.authProfile) {
|
|
4883
|
+
assertCodewithAuthProfileSupported(agentTarget.authProfile);
|
|
4884
|
+
}
|
|
4849
4885
|
const adapter = providerAdapter(agentTarget.provider);
|
|
4850
4886
|
const invocation = adapter.buildInvocation(agentTarget);
|
|
4851
4887
|
return {
|
|
@@ -5018,7 +5054,8 @@ function remotePreflightScript(spec, metadata) {
|
|
|
5018
5054
|
lines.push(`if ! ${spec.preflightAnyOf.map((command) => `command -v ${shellQuote(command)} >/dev/null 2>&1`).join(" && ! ")}; then`, ` echo 'none of required executables found: ${spec.preflightAnyOf.join(", ")}' >&2`, " exit 127", "fi");
|
|
5019
5055
|
}
|
|
5020
5056
|
if (spec.nativeAuthProfile?.provider === "codewith") {
|
|
5021
|
-
|
|
5057
|
+
const profileForError = codewithProfileForError(spec.nativeAuthProfile.profile);
|
|
5058
|
+
lines.push(`__OPENLOOPS_CODEWITH_PROFILE=${shellQuote(spec.nativeAuthProfile.profile)}`, "export __OPENLOOPS_CODEWITH_PROFILE", `__OPENLOOPS_CODEWITH_PROFILES="$(${shellQuote(spec.command)} profile list)" || {`, ` printf '%s\\n' ${shellQuote("codewith auth profile preflight failed")} >&2`, " exit 1", "}", `if ! printf '%s\\n' "$__OPENLOOPS_CODEWITH_PROFILES" | awk 'NR > 1 { candidate = ($1 == "*" ? $2 : $1); if (candidate == ENVIRON["__OPENLOOPS_CODEWITH_PROFILE"]) found = 1 } END { exit(found ? 0 : 1) }'; then`, ` printf '%s\\n' ${shellQuote(`codewith auth profile not found: ${profileForError}`)} >&2`, " exit 1", "fi");
|
|
5022
5059
|
}
|
|
5023
5060
|
return lines.join(`
|
|
5024
5061
|
`);
|
|
@@ -5043,9 +5080,9 @@ function assertCodewithProfileListed(profile, result) {
|
|
|
5043
5080
|
const detail = (result.stderr || result.stdout || `exit ${result.status ?? "unknown"}`).trim();
|
|
5044
5081
|
throw new Error(`codewith auth profile preflight failed${detail ? `: ${detail}` : ""}`);
|
|
5045
5082
|
}
|
|
5046
|
-
const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map(
|
|
5083
|
+
const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map(codewithProfileCandidateFromLine).filter(Boolean));
|
|
5047
5084
|
if (!profiles.has(profile)) {
|
|
5048
|
-
throw new Error(`codewith auth profile not found: ${profile}`);
|
|
5085
|
+
throw new Error(`codewith auth profile not found: ${codewithProfileForError(profile)}`);
|
|
5049
5086
|
}
|
|
5050
5087
|
}
|
|
5051
5088
|
function preflightNativeAuthProfileSync(spec, env) {
|
|
@@ -6984,6 +7021,9 @@ async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
|
6984
7021
|
}
|
|
6985
7022
|
|
|
6986
7023
|
// src/lib/scheduler.ts
|
|
7024
|
+
function loopLane(loop) {
|
|
7025
|
+
return loop.target.type === "command" ? "command" : "agent";
|
|
7026
|
+
}
|
|
6987
7027
|
function manualRunScheduledFor(loop, now = new Date) {
|
|
6988
7028
|
if (loop.archivedAt)
|
|
6989
7029
|
return now.toISOString();
|
|
@@ -7376,14 +7416,23 @@ function claimDueRuns(deps) {
|
|
|
7376
7416
|
const maxClaims = Math.max(0, deps.maxClaims ?? Number.POSITIVE_INFINITY);
|
|
7377
7417
|
if (maxClaims === 0)
|
|
7378
7418
|
return { claims, claimed, completed: [], skipped, recovered, expired };
|
|
7419
|
+
const laneLimits = deps.laneLimits;
|
|
7420
|
+
const laneClaims = { command: 0, agent: 0 };
|
|
7421
|
+
const laneCap = (lane) => laneLimits === undefined ? Number.POSITIVE_INFINITY : Math.max(0, laneLimits[lane] ?? Number.POSITIVE_INFINITY);
|
|
7422
|
+
const laneFull = (lane) => laneClaims[lane] >= laneCap(lane);
|
|
7379
7423
|
for (const loop of deps.store.dueLoops(now)) {
|
|
7380
7424
|
if (claims.length >= maxClaims)
|
|
7381
7425
|
break;
|
|
7426
|
+
const lane = loopLane(loop);
|
|
7427
|
+
if (laneFull(lane))
|
|
7428
|
+
continue;
|
|
7382
7429
|
const plan = dueSlots(loop, now);
|
|
7383
7430
|
let loopSkips = 0;
|
|
7384
7431
|
for (const slot of plan.slots) {
|
|
7385
7432
|
if (claims.length >= maxClaims)
|
|
7386
7433
|
break;
|
|
7434
|
+
if (laneFull(lane))
|
|
7435
|
+
break;
|
|
7387
7436
|
if (loopSkips >= MAX_SKIPS_PER_LOOP_PER_TICK)
|
|
7388
7437
|
break;
|
|
7389
7438
|
const run = claimSlot(deps, loop, slot);
|
|
@@ -7392,6 +7441,7 @@ function claimDueRuns(deps) {
|
|
|
7392
7441
|
if ("loop" in run) {
|
|
7393
7442
|
claims.push(run);
|
|
7394
7443
|
claimed.push(run.run);
|
|
7444
|
+
laneClaims[lane] += 1;
|
|
7395
7445
|
} else if (run.status === "skipped") {
|
|
7396
7446
|
skipped.push(run);
|
|
7397
7447
|
loopSkips += 1;
|
|
@@ -7431,7 +7481,7 @@ async function tick(deps) {
|
|
|
7431
7481
|
// package.json
|
|
7432
7482
|
var package_default = {
|
|
7433
7483
|
name: "@hasna/loops",
|
|
7434
|
-
version: "0.4.
|
|
7484
|
+
version: "0.4.11",
|
|
7435
7485
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
7436
7486
|
type: "module",
|
|
7437
7487
|
main: "dist/index.js",
|
package/dist/runner/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hasna/loops",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.11",
|
|
7
7
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
8
8
|
type: "module",
|
|
9
9
|
main: "dist/index.js",
|
|
@@ -1077,6 +1077,20 @@ function notifySpawn(pid, opts) {
|
|
|
1077
1077
|
function shellQuote(value) {
|
|
1078
1078
|
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
1079
1079
|
}
|
|
1080
|
+
function codewithProfileCandidateFromLine(line) {
|
|
1081
|
+
const cols = line.trim().split(/\s+/);
|
|
1082
|
+
if (cols[0] === "*")
|
|
1083
|
+
return cols[1];
|
|
1084
|
+
return cols[0];
|
|
1085
|
+
}
|
|
1086
|
+
function codewithProfileForError(profile) {
|
|
1087
|
+
return /[\u0000-\u001F\u007F]/.test(profile) ? JSON.stringify(profile) ?? profile : profile;
|
|
1088
|
+
}
|
|
1089
|
+
function assertCodewithAuthProfileSupported(profile) {
|
|
1090
|
+
if (profile.includes("\x00")) {
|
|
1091
|
+
throw new Error(`codewith auth profile contains unsupported NUL byte: ${codewithProfileForError(profile)}`);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1080
1094
|
function metadataEnv(metadata) {
|
|
1081
1095
|
const env = {};
|
|
1082
1096
|
if (metadata.loopId)
|
|
@@ -1143,6 +1157,9 @@ function commandSpec(target, opts) {
|
|
|
1143
1157
|
};
|
|
1144
1158
|
}
|
|
1145
1159
|
const agentTarget = target;
|
|
1160
|
+
if (agentTarget.provider === "codewith" && agentTarget.authProfile) {
|
|
1161
|
+
assertCodewithAuthProfileSupported(agentTarget.authProfile);
|
|
1162
|
+
}
|
|
1146
1163
|
const adapter = providerAdapter(agentTarget.provider);
|
|
1147
1164
|
const invocation = adapter.buildInvocation(agentTarget);
|
|
1148
1165
|
return {
|
|
@@ -1315,7 +1332,8 @@ function remotePreflightScript(spec, metadata) {
|
|
|
1315
1332
|
lines.push(`if ! ${spec.preflightAnyOf.map((command) => `command -v ${shellQuote(command)} >/dev/null 2>&1`).join(" && ! ")}; then`, ` echo 'none of required executables found: ${spec.preflightAnyOf.join(", ")}' >&2`, " exit 127", "fi");
|
|
1316
1333
|
}
|
|
1317
1334
|
if (spec.nativeAuthProfile?.provider === "codewith") {
|
|
1318
|
-
|
|
1335
|
+
const profileForError = codewithProfileForError(spec.nativeAuthProfile.profile);
|
|
1336
|
+
lines.push(`__OPENLOOPS_CODEWITH_PROFILE=${shellQuote(spec.nativeAuthProfile.profile)}`, "export __OPENLOOPS_CODEWITH_PROFILE", `__OPENLOOPS_CODEWITH_PROFILES="$(${shellQuote(spec.command)} profile list)" || {`, ` printf '%s\\n' ${shellQuote("codewith auth profile preflight failed")} >&2`, " exit 1", "}", `if ! printf '%s\\n' "$__OPENLOOPS_CODEWITH_PROFILES" | awk 'NR > 1 { candidate = ($1 == "*" ? $2 : $1); if (candidate == ENVIRON["__OPENLOOPS_CODEWITH_PROFILE"]) found = 1 } END { exit(found ? 0 : 1) }'; then`, ` printf '%s\\n' ${shellQuote(`codewith auth profile not found: ${profileForError}`)} >&2`, " exit 1", "fi");
|
|
1319
1337
|
}
|
|
1320
1338
|
return lines.join(`
|
|
1321
1339
|
`);
|
|
@@ -1340,9 +1358,9 @@ function assertCodewithProfileListed(profile, result) {
|
|
|
1340
1358
|
const detail = (result.stderr || result.stdout || `exit ${result.status ?? "unknown"}`).trim();
|
|
1341
1359
|
throw new Error(`codewith auth profile preflight failed${detail ? `: ${detail}` : ""}`);
|
|
1342
1360
|
}
|
|
1343
|
-
const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map(
|
|
1361
|
+
const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map(codewithProfileCandidateFromLine).filter(Boolean));
|
|
1344
1362
|
if (!profiles.has(profile)) {
|
|
1345
|
-
throw new Error(`codewith auth profile not found: ${profile}`);
|
|
1363
|
+
throw new Error(`codewith auth profile not found: ${codewithProfileForError(profile)}`);
|
|
1346
1364
|
}
|
|
1347
1365
|
}
|
|
1348
1366
|
function preflightNativeAuthProfileSync(spec, env) {
|