@hasna/loops 0.3.45 → 0.3.47
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 +4 -2
- package/dist/cli/index.js +37 -4
- package/dist/daemon/index.js +10 -4
- package/dist/index.js +9 -3
- package/dist/lib/store.js +3 -0
- package/dist/sdk/index.js +9 -3
- package/docs/USAGE.md +4 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -299,7 +299,9 @@ worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
|
299
299
|
For task-created routes, pass `--todos-project` so worker/verifier prompts use
|
|
300
300
|
the actual todos storage project while repo routing and worktree isolation still
|
|
301
301
|
use the repository path. This avoids `danger-full-access` for normal todos
|
|
302
|
-
comments, completion state, and loop evidence writes.
|
|
302
|
+
comments, completion state, and loop evidence writes. `addDirs` is intentionally
|
|
303
|
+
accepted only for Codewith/Codex until other providers expose equivalent
|
|
304
|
+
directory-scoped write controls.
|
|
303
305
|
|
|
304
306
|
Inspect route state with:
|
|
305
307
|
|
|
@@ -497,7 +499,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
497
499
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
498
500
|
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
499
501
|
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
500
|
-
- Cursor is CLI-first for now via `
|
|
502
|
+
- Cursor is CLI-first for now via standalone `agent -p` when available, with `cursor agent -p` as a compatibility fallback; treat output as less stable until a stronger public SDK contract is selected.
|
|
501
503
|
- Codex uses `codex exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
502
504
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|
|
503
505
|
- When `--account` or a step `account` is set, OpenLoops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
|
package/dist/cli/index.js
CHANGED
|
@@ -389,6 +389,9 @@ function validateTarget(value, label) {
|
|
|
389
389
|
if (value.variant !== undefined)
|
|
390
390
|
assertString(value.variant, `${label}.variant`);
|
|
391
391
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
392
|
+
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
393
|
+
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
394
|
+
}
|
|
392
395
|
if (value.permissionMode !== undefined) {
|
|
393
396
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
394
397
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3154,6 +3157,9 @@ function assertSupportedAgentOptions(target) {
|
|
|
3154
3157
|
if (target.authProfile !== undefined && target.provider !== "codewith") {
|
|
3155
3158
|
throw new Error(`${target.provider}.authProfile is supported only for codewith`);
|
|
3156
3159
|
}
|
|
3160
|
+
if (target.addDirs?.length && !["codewith", "codex"].includes(target.provider)) {
|
|
3161
|
+
throw new Error(`${target.provider}.addDirs is currently supported only for codewith or codex`);
|
|
3162
|
+
}
|
|
3157
3163
|
if (target.permissionMode && !["default", "plan", "auto", "bypass"].includes(target.permissionMode)) {
|
|
3158
3164
|
throw new Error(`${target.provider}.permissionMode must be default, plan, auto, or bypass`);
|
|
3159
3165
|
}
|
|
@@ -3213,10 +3219,10 @@ function agentArgs(target) {
|
|
|
3213
3219
|
case "cursor":
|
|
3214
3220
|
args.push("-c", [
|
|
3215
3221
|
"set -eu",
|
|
3216
|
-
"if command -v
|
|
3217
|
-
' exec cursor agent "$@"',
|
|
3218
|
-
"elif command -v agent >/dev/null 2>&1; then",
|
|
3222
|
+
"if command -v agent >/dev/null 2>&1; then",
|
|
3219
3223
|
' exec agent "$@"',
|
|
3224
|
+
"elif command -v cursor >/dev/null 2>&1; then",
|
|
3225
|
+
' exec cursor agent "$@"',
|
|
3220
3226
|
"else",
|
|
3221
3227
|
" echo 'Executable not found in PATH: cursor agent or agent' >&2",
|
|
3222
3228
|
" exit 127",
|
|
@@ -5737,7 +5743,7 @@ function buildScriptInventoryReport(store, opts = {}) {
|
|
|
5737
5743
|
// package.json
|
|
5738
5744
|
var package_default = {
|
|
5739
5745
|
name: "@hasna/loops",
|
|
5740
|
-
version: "0.3.
|
|
5746
|
+
version: "0.3.47",
|
|
5741
5747
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5742
5748
|
type: "module",
|
|
5743
5749
|
main: "dist/index.js",
|
|
@@ -6803,6 +6809,31 @@ function preflightFailed(error, context) {
|
|
|
6803
6809
|
});
|
|
6804
6810
|
process.exit(1);
|
|
6805
6811
|
}
|
|
6812
|
+
function validationFailed(error, context) {
|
|
6813
|
+
if (!isJson())
|
|
6814
|
+
throw error;
|
|
6815
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
6816
|
+
print({
|
|
6817
|
+
ok: false,
|
|
6818
|
+
created: false,
|
|
6819
|
+
validation: {
|
|
6820
|
+
ok: false,
|
|
6821
|
+
error: redact(message, 320)
|
|
6822
|
+
},
|
|
6823
|
+
...context
|
|
6824
|
+
});
|
|
6825
|
+
process.exit(1);
|
|
6826
|
+
}
|
|
6827
|
+
function validateLoopTargetForStorage(target, context) {
|
|
6828
|
+
try {
|
|
6829
|
+
workflowBodyFromJson({
|
|
6830
|
+
name: "loop-target-validation",
|
|
6831
|
+
steps: [{ id: "target", target }]
|
|
6832
|
+
});
|
|
6833
|
+
} catch (error) {
|
|
6834
|
+
validationFailed(error, context);
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6806
6837
|
function preflightLoopTarget(target, context, metadata, opts) {
|
|
6807
6838
|
try {
|
|
6808
6839
|
return preflightTarget(target, metadata, opts);
|
|
@@ -8165,6 +8196,7 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
8165
8196
|
preflight: runtimePreflightFromOpts(opts)
|
|
8166
8197
|
};
|
|
8167
8198
|
const input = baseCreateInput(name, opts, target);
|
|
8199
|
+
validateLoopTargetForStorage(input.target, { name, type: "agent", provider });
|
|
8168
8200
|
const preflight = opts.preflight ? preflightLoopTarget(input.target, { name, type: "agent", provider }, { loopName: name }, { machine: input.machine }) : undefined;
|
|
8169
8201
|
const loop = store.createLoop(input);
|
|
8170
8202
|
printCreatedLoop(loop, `created loop ${loop.id} (${loop.name}) next=${loop.nextRunAt}`, preflight);
|
|
@@ -8593,6 +8625,7 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
|
|
|
8593
8625
|
model: opts.model,
|
|
8594
8626
|
variant: opts.variant,
|
|
8595
8627
|
agent: opts.agent,
|
|
8628
|
+
addDirs: listFromRepeatedOpts(opts.addDir),
|
|
8596
8629
|
permissionMode,
|
|
8597
8630
|
sandbox,
|
|
8598
8631
|
manualBreakGlass: Boolean(opts.manualBreakGlass),
|
package/dist/daemon/index.js
CHANGED
|
@@ -389,6 +389,9 @@ function validateTarget(value, label) {
|
|
|
389
389
|
if (value.variant !== undefined)
|
|
390
390
|
assertString(value.variant, `${label}.variant`);
|
|
391
391
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
392
|
+
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
393
|
+
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
394
|
+
}
|
|
392
395
|
if (value.permissionMode !== undefined) {
|
|
393
396
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
394
397
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3038,6 +3041,9 @@ function assertSupportedAgentOptions(target) {
|
|
|
3038
3041
|
if (target.authProfile !== undefined && target.provider !== "codewith") {
|
|
3039
3042
|
throw new Error(`${target.provider}.authProfile is supported only for codewith`);
|
|
3040
3043
|
}
|
|
3044
|
+
if (target.addDirs?.length && !["codewith", "codex"].includes(target.provider)) {
|
|
3045
|
+
throw new Error(`${target.provider}.addDirs is currently supported only for codewith or codex`);
|
|
3046
|
+
}
|
|
3041
3047
|
if (target.permissionMode && !["default", "plan", "auto", "bypass"].includes(target.permissionMode)) {
|
|
3042
3048
|
throw new Error(`${target.provider}.permissionMode must be default, plan, auto, or bypass`);
|
|
3043
3049
|
}
|
|
@@ -3097,10 +3103,10 @@ function agentArgs(target) {
|
|
|
3097
3103
|
case "cursor":
|
|
3098
3104
|
args.push("-c", [
|
|
3099
3105
|
"set -eu",
|
|
3100
|
-
"if command -v
|
|
3101
|
-
' exec cursor agent "$@"',
|
|
3102
|
-
"elif command -v agent >/dev/null 2>&1; then",
|
|
3106
|
+
"if command -v agent >/dev/null 2>&1; then",
|
|
3103
3107
|
' exec agent "$@"',
|
|
3108
|
+
"elif command -v cursor >/dev/null 2>&1; then",
|
|
3109
|
+
' exec cursor agent "$@"',
|
|
3104
3110
|
"else",
|
|
3105
3111
|
" echo 'Executable not found in PATH: cursor agent or agent' >&2",
|
|
3106
3112
|
" exit 127",
|
|
@@ -5051,7 +5057,7 @@ function enableStartup(result) {
|
|
|
5051
5057
|
// package.json
|
|
5052
5058
|
var package_default = {
|
|
5053
5059
|
name: "@hasna/loops",
|
|
5054
|
-
version: "0.3.
|
|
5060
|
+
version: "0.3.47",
|
|
5055
5061
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5056
5062
|
type: "module",
|
|
5057
5063
|
main: "dist/index.js",
|
package/dist/index.js
CHANGED
|
@@ -387,6 +387,9 @@ function validateTarget(value, label) {
|
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
389
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
390
|
+
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
391
|
+
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
392
|
+
}
|
|
390
393
|
if (value.permissionMode !== undefined) {
|
|
391
394
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
392
395
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3028,6 +3031,9 @@ function assertSupportedAgentOptions(target) {
|
|
|
3028
3031
|
if (target.authProfile !== undefined && target.provider !== "codewith") {
|
|
3029
3032
|
throw new Error(`${target.provider}.authProfile is supported only for codewith`);
|
|
3030
3033
|
}
|
|
3034
|
+
if (target.addDirs?.length && !["codewith", "codex"].includes(target.provider)) {
|
|
3035
|
+
throw new Error(`${target.provider}.addDirs is currently supported only for codewith or codex`);
|
|
3036
|
+
}
|
|
3031
3037
|
if (target.permissionMode && !["default", "plan", "auto", "bypass"].includes(target.permissionMode)) {
|
|
3032
3038
|
throw new Error(`${target.provider}.permissionMode must be default, plan, auto, or bypass`);
|
|
3033
3039
|
}
|
|
@@ -3087,10 +3093,10 @@ function agentArgs(target) {
|
|
|
3087
3093
|
case "cursor":
|
|
3088
3094
|
args.push("-c", [
|
|
3089
3095
|
"set -eu",
|
|
3090
|
-
"if command -v
|
|
3091
|
-
' exec cursor agent "$@"',
|
|
3092
|
-
"elif command -v agent >/dev/null 2>&1; then",
|
|
3096
|
+
"if command -v agent >/dev/null 2>&1; then",
|
|
3093
3097
|
' exec agent "$@"',
|
|
3098
|
+
"elif command -v cursor >/dev/null 2>&1; then",
|
|
3099
|
+
' exec cursor agent "$@"',
|
|
3094
3100
|
"else",
|
|
3095
3101
|
" echo 'Executable not found in PATH: cursor agent or agent' >&2",
|
|
3096
3102
|
" exit 127",
|
package/dist/lib/store.js
CHANGED
|
@@ -387,6 +387,9 @@ function validateTarget(value, label) {
|
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
389
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
390
|
+
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
391
|
+
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
392
|
+
}
|
|
390
393
|
if (value.permissionMode !== undefined) {
|
|
391
394
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
392
395
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
package/dist/sdk/index.js
CHANGED
|
@@ -387,6 +387,9 @@ function validateTarget(value, label) {
|
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
389
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
390
|
+
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
391
|
+
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
392
|
+
}
|
|
390
393
|
if (value.permissionMode !== undefined) {
|
|
391
394
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
392
395
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3028,6 +3031,9 @@ function assertSupportedAgentOptions(target) {
|
|
|
3028
3031
|
if (target.authProfile !== undefined && target.provider !== "codewith") {
|
|
3029
3032
|
throw new Error(`${target.provider}.authProfile is supported only for codewith`);
|
|
3030
3033
|
}
|
|
3034
|
+
if (target.addDirs?.length && !["codewith", "codex"].includes(target.provider)) {
|
|
3035
|
+
throw new Error(`${target.provider}.addDirs is currently supported only for codewith or codex`);
|
|
3036
|
+
}
|
|
3031
3037
|
if (target.permissionMode && !["default", "plan", "auto", "bypass"].includes(target.permissionMode)) {
|
|
3032
3038
|
throw new Error(`${target.provider}.permissionMode must be default, plan, auto, or bypass`);
|
|
3033
3039
|
}
|
|
@@ -3087,10 +3093,10 @@ function agentArgs(target) {
|
|
|
3087
3093
|
case "cursor":
|
|
3088
3094
|
args.push("-c", [
|
|
3089
3095
|
"set -eu",
|
|
3090
|
-
"if command -v
|
|
3091
|
-
' exec cursor agent "$@"',
|
|
3092
|
-
"elif command -v agent >/dev/null 2>&1; then",
|
|
3096
|
+
"if command -v agent >/dev/null 2>&1; then",
|
|
3093
3097
|
' exec agent "$@"',
|
|
3098
|
+
"elif command -v cursor >/dev/null 2>&1; then",
|
|
3099
|
+
' exec cursor agent "$@"',
|
|
3094
3100
|
"else",
|
|
3095
3101
|
" echo 'Executable not found in PATH: cursor agent or agent' >&2",
|
|
3096
3102
|
" exit 127",
|
package/docs/USAGE.md
CHANGED
|
@@ -331,7 +331,9 @@ worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
|
331
331
|
For task-created routes, pass `--todos-project` so worker/verifier prompts use
|
|
332
332
|
the actual todos storage project while route concurrency and worktree isolation
|
|
333
333
|
still use the repository path. This avoids `danger-full-access` for normal
|
|
334
|
-
todos comments, completion state, and loop evidence writes.
|
|
334
|
+
todos comments, completion state, and loop evidence writes. `addDirs` is
|
|
335
|
+
intentionally accepted only for Codewith/Codex until other providers expose
|
|
336
|
+
equivalent directory-scoped write controls.
|
|
335
337
|
|
|
336
338
|
Inspect route state with:
|
|
337
339
|
|
|
@@ -576,7 +578,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
|
|
|
576
578
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
577
579
|
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
578
580
|
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
579
|
-
- Cursor is CLI-first for now via `
|
|
581
|
+
- Cursor is CLI-first for now via standalone `agent -p` when available, with `cursor agent -p` as a compatibility fallback; treat output as less stable until a stronger public SDK contract is selected.
|
|
580
582
|
- Codex uses `codex exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
581
583
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|
|
582
584
|
- When `--account` or a step `account` is set, OpenLoops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
|
package/package.json
CHANGED