@openwop/cli 1.6.0 → 1.8.0
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/cli.js +135 -35
- package/dist/openwop.js +133 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -498,7 +498,7 @@ The `goals` group drives the host's standing goals — an objective the host pur
|
|
|
498
498
|
openwop goals list --state active # the goal list (also satisfied | escalated | bound-exceeded | paused)
|
|
499
499
|
openwop goals get goal_123 --json # inspect (objective, judge, continuation, bounds, iterations)
|
|
500
500
|
openwop goals create --objective "Keep the triage backlog under 20" \
|
|
501
|
-
--
|
|
501
|
+
--verifier-ref <ref> --continuation schedule --max-iterations 50 --max-cost 5.00 --timeout-ms 600000
|
|
502
502
|
openwop goals pause goal_123 # suspend continuation (resume to restart)
|
|
503
503
|
openwop goals abandon goal_123 --yes # close the goal
|
|
504
504
|
```
|
package/dist/cli.js
CHANGED
|
@@ -578,7 +578,7 @@ function errText(err) {
|
|
|
578
578
|
// package.json
|
|
579
579
|
var package_default = {
|
|
580
580
|
name: "@openwop/cli",
|
|
581
|
-
version: "1.
|
|
581
|
+
version: "1.8.0",
|
|
582
582
|
description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
|
|
583
583
|
license: "Apache-2.0",
|
|
584
584
|
type: "module",
|
|
@@ -855,21 +855,31 @@ function mergeConfig(existing, next) {
|
|
|
855
855
|
function getByPath(obj, path) {
|
|
856
856
|
return path.split(".").reduce((acc, key) => acc == null ? acc : acc[key], obj);
|
|
857
857
|
}
|
|
858
|
-
|
|
858
|
+
var UNSAFE_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
859
|
+
function safePathParts(path) {
|
|
859
860
|
const parts = path.split(".");
|
|
861
|
+
for (const part of parts) {
|
|
862
|
+
if (UNSAFE_PATH_SEGMENTS.has(part)) throw new CliError(`Config key segment '${part}' is not allowed: ${path}`);
|
|
863
|
+
}
|
|
864
|
+
return parts;
|
|
865
|
+
}
|
|
866
|
+
function setByPath(obj, path, value) {
|
|
867
|
+
const parts = safePathParts(path);
|
|
860
868
|
let cur = obj;
|
|
861
869
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
862
|
-
|
|
863
|
-
cur
|
|
870
|
+
const key = parts[i];
|
|
871
|
+
if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
872
|
+
cur = cur[key];
|
|
864
873
|
}
|
|
865
874
|
cur[parts[parts.length - 1]] = value;
|
|
866
875
|
}
|
|
867
876
|
function unsetByPath(obj, path) {
|
|
868
|
-
const parts = path
|
|
877
|
+
const parts = safePathParts(path);
|
|
869
878
|
let cur = obj;
|
|
870
879
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
871
|
-
|
|
872
|
-
cur
|
|
880
|
+
const key = parts[i];
|
|
881
|
+
if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) return;
|
|
882
|
+
cur = cur[key];
|
|
873
883
|
}
|
|
874
884
|
delete cur[parts[parts.length - 1]];
|
|
875
885
|
}
|
|
@@ -1096,12 +1106,22 @@ function decodeSegment(segment) {
|
|
|
1096
1106
|
return segment;
|
|
1097
1107
|
}
|
|
1098
1108
|
}
|
|
1109
|
+
function splitRunSegment(bare) {
|
|
1110
|
+
const PREFIX = "/runs/";
|
|
1111
|
+
if (!bare.startsWith(PREFIX)) return null;
|
|
1112
|
+
let end = PREFIX.length;
|
|
1113
|
+
while (end < bare.length && bare[end] !== "/" && bare[end] !== ":") end++;
|
|
1114
|
+
if (end === PREFIX.length) return null;
|
|
1115
|
+
const rest = bare.slice(end);
|
|
1116
|
+
if (/[\n\r\u2028\u2029]/.test(rest)) return null;
|
|
1117
|
+
return [bare.slice(PREFIX.length, end), rest];
|
|
1118
|
+
}
|
|
1099
1119
|
function projectRunIdsInPath(path) {
|
|
1100
1120
|
const q = path.indexOf("?");
|
|
1101
1121
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
1102
1122
|
let query = q === -1 ? "" : path.slice(q);
|
|
1103
|
-
const m =
|
|
1104
|
-
const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[
|
|
1123
|
+
const m = splitRunSegment(bare);
|
|
1124
|
+
const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[0]))}${m[1]}` : bare;
|
|
1105
1125
|
if (m && query) {
|
|
1106
1126
|
query = query.replace(/([?&]against=)([^&]*)/, (_all, key, value) => `${key}${projectTenantBoundId(decodeSegment(value))}`);
|
|
1107
1127
|
}
|
|
@@ -1167,9 +1187,11 @@ function v2SeamTwin(path) {
|
|
|
1167
1187
|
}
|
|
1168
1188
|
return null;
|
|
1169
1189
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
)
|
|
1190
|
+
function v2TemplatePattern(template) {
|
|
1191
|
+
const literals = template.split(/\{[A-Za-z]+\}/);
|
|
1192
|
+
return `^${literals.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`;
|
|
1193
|
+
}
|
|
1194
|
+
var V2_MATCHERS = V2_PATH_TEMPLATES.map((t) => new RegExp(v2TemplatePattern(t)));
|
|
1173
1195
|
function v2Twin(path) {
|
|
1174
1196
|
const q = path.indexOf("?");
|
|
1175
1197
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
@@ -1275,7 +1297,10 @@ function explicitStreamBase(raw) {
|
|
|
1275
1297
|
if (url.username !== "" || url.password !== "") {
|
|
1276
1298
|
throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
|
|
1277
1299
|
}
|
|
1278
|
-
|
|
1300
|
+
const base16 = raw.trim();
|
|
1301
|
+
let end = base16.length;
|
|
1302
|
+
while (end > 0 && base16[end - 1] === "/") end--;
|
|
1303
|
+
return base16.slice(0, end);
|
|
1279
1304
|
}
|
|
1280
1305
|
function streamBaseFrom(extensions, baseUrl) {
|
|
1281
1306
|
if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
|
|
@@ -20476,7 +20501,7 @@ var GOALS_ROUTES = [
|
|
|
20476
20501
|
var GOALS_HELP = `Usage:
|
|
20477
20502
|
openwop goals list [--state <s>] [--json]
|
|
20478
20503
|
openwop goals get <goalId> [--json]
|
|
20479
|
-
openwop goals create --objective <text>
|
|
20504
|
+
openwop goals create --objective <text> --verifier-ref <ref> [--continuation schedule|manual]
|
|
20480
20505
|
[--max-iterations <n>] [--max-cost <usd>] [--timeout-ms <ms>] [--json]
|
|
20481
20506
|
openwop goals pause <goalId> [--json]
|
|
20482
20507
|
openwop goals resume <goalId> [--json]
|
|
@@ -20503,13 +20528,16 @@ Endpoints:
|
|
|
20503
20528
|
resume POST /v1/host/openwop-app/goals/{id}/resume
|
|
20504
20529
|
abandon POST /v1/host/openwop-app/goals/{id}/abandon
|
|
20505
20530
|
|
|
20506
|
-
--state <s> (list) Filter by state
|
|
20531
|
+
--state <s> (list) Filter by state: active, satisfied, escalated, abandoned, bound-exceeded.
|
|
20507
20532
|
--objective <text> (create) The goal objective (required).
|
|
20508
|
-
--
|
|
20509
|
-
|
|
20510
|
-
--
|
|
20533
|
+
--verifier-ref <ref> (create) completion.verifierRef \u2014 the verifier registered on the host that
|
|
20534
|
+
judges this goal (required; the host refuses a ref it has not registered).
|
|
20535
|
+
--judge verifier (create) completion.check \u2014 who decides completion. 'verifier' is the only
|
|
20536
|
+
judge a host advertising agents.goals.judge=verifier honours (the default).
|
|
20537
|
+
--continuation <mode> (create) continuation.mode \u2014 schedule | manual (default: manual).
|
|
20538
|
+
--max-iterations <n> (create) Bound: bounds.maxLoopIterations (positive integer).
|
|
20511
20539
|
--max-cost <usd> (create) Bound: bounds.maxCostUsd (accumulated USD).
|
|
20512
|
-
--timeout-ms <ms> (create) Bound: bounds.runTimeoutMs (wall-clock, milliseconds).
|
|
20540
|
+
--timeout-ms <ms> (create) Bound: bounds.runTimeoutMs (wall-clock, positive milliseconds).
|
|
20513
20541
|
--yes (abandon) Required to confirm.
|
|
20514
20542
|
--json Print the raw host response instead of the rendered view.
|
|
20515
20543
|
|
|
@@ -20519,13 +20547,14 @@ termination guarantee (bounds).
|
|
|
20519
20547
|
|
|
20520
20548
|
Exit codes (get/create/pause/resume reflect the goal's state, so scripts can gate
|
|
20521
20549
|
on the verdict):
|
|
20522
|
-
0 satisfied 3 escalated or still open (active
|
|
20550
|
+
0 satisfied 3 escalated or still open (active) 1 bound-exceeded/abandoned or error
|
|
20551
|
+
A paused goal is still 'active' (pause stops its continuation, not the goal).
|
|
20523
20552
|
\`list\` exits 0 on success.
|
|
20524
20553
|
|
|
20525
20554
|
Examples:
|
|
20526
20555
|
openwop goals list --state active
|
|
20527
20556
|
openwop goals get goal_123 --json
|
|
20528
|
-
openwop goals create --objective "Keep the triage backlog under 20" --
|
|
20557
|
+
openwop goals create --objective "Keep the triage backlog under 20" --verifier-ref <ref> --continuation schedule --max-iterations 50 --max-cost 5.00 --timeout-ms 600000
|
|
20529
20558
|
openwop goals pause goal_123
|
|
20530
20559
|
openwop goals abandon goal_123 --yes
|
|
20531
20560
|
`;
|
|
@@ -20671,18 +20700,24 @@ function renderGoal(ctx, g, fallbackId) {
|
|
|
20671
20700
|
async function runGoalsCreate(ctx, argv) {
|
|
20672
20701
|
const { options } = parseOptions(argv, {
|
|
20673
20702
|
bool: ["--help"],
|
|
20674
|
-
value: ["--objective", "--judge", "--continuation", "--max-iterations", "--max-cost", "--timeout-ms"]
|
|
20703
|
+
value: ["--objective", "--judge", "--verifier-ref", "--continuation", "--max-iterations", "--max-cost", "--timeout-ms"]
|
|
20675
20704
|
});
|
|
20676
20705
|
if (options.help || !options.objective) {
|
|
20677
|
-
write(ctx.io.stdout, "Usage: openwop goals create --objective <text>
|
|
20706
|
+
write(ctx.io.stdout, "Usage: openwop goals create --objective <text> --verifier-ref <ref> [--continuation schedule|manual] [--max-iterations <n>] [--max-cost <usd>] [--timeout-ms <ms>] [--json]\n");
|
|
20678
20707
|
return options.help ? 0 : 2;
|
|
20679
20708
|
}
|
|
20680
|
-
if (options.judge !== void 0 &&
|
|
20681
|
-
throw new CliError("--judge must be
|
|
20709
|
+
if (options.judge !== void 0 && options.judge !== "verifier") {
|
|
20710
|
+
throw new CliError("--judge must be: verifier", 2);
|
|
20711
|
+
}
|
|
20712
|
+
if (!options.verifierRef) {
|
|
20713
|
+
throw new CliError("goals create needs --verifier-ref <ref> \u2014 the verifier registered on the host that judges the goal", 2);
|
|
20714
|
+
}
|
|
20715
|
+
if (options.continuation !== void 0 && !["schedule", "manual"].includes(options.continuation)) {
|
|
20716
|
+
throw new CliError("--continuation must be one of: schedule, manual", 2);
|
|
20682
20717
|
}
|
|
20683
20718
|
const body = {
|
|
20684
20719
|
objective: options.objective,
|
|
20685
|
-
completion: { check: options.
|
|
20720
|
+
completion: { check: "verifier", verifierRef: options.verifierRef },
|
|
20686
20721
|
continuation: { mode: options.continuation ?? "manual" }
|
|
20687
20722
|
};
|
|
20688
20723
|
const bounds = {};
|
|
@@ -20698,7 +20733,7 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20698
20733
|
}
|
|
20699
20734
|
if (options.timeoutMs !== void 0) {
|
|
20700
20735
|
const t = Number(options.timeoutMs);
|
|
20701
|
-
if (!Number.isInteger(t) || t
|
|
20736
|
+
if (!Number.isInteger(t) || t <= 0) throw new CliError("--timeout-ms must be a positive integer (milliseconds)", 2);
|
|
20702
20737
|
bounds.runTimeoutMs = t;
|
|
20703
20738
|
}
|
|
20704
20739
|
if (Object.keys(bounds).length) body.bounds = bounds;
|
|
@@ -20707,8 +20742,8 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20707
20742
|
try {
|
|
20708
20743
|
res = await requestJson(ctx, "/v1/host/openwop-app/goals", { method: "POST", body });
|
|
20709
20744
|
} catch (err) {
|
|
20710
|
-
if (err instanceof HttpError && err.status === 422) {
|
|
20711
|
-
const detail2 = err.body?.message ?? "this host requires bounds on an active goal \u2014 pass --max-iterations, --max-cost, and/or --timeout-ms";
|
|
20745
|
+
if (err instanceof HttpError && (err.status === 422 || err.status === 400)) {
|
|
20746
|
+
const detail2 = err.body?.message ?? (err.status === 422 ? "this host requires bounds on an active goal \u2014 pass --max-iterations, --max-cost, and/or --timeout-ms" : "the host refused the goal as malformed");
|
|
20712
20747
|
throw new CliError(`goals: ${detail2}`, 1);
|
|
20713
20748
|
}
|
|
20714
20749
|
gate4045(err);
|
|
@@ -20718,7 +20753,7 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20718
20753
|
writeJson(ctx.io.stdout, g);
|
|
20719
20754
|
return exitForState2(g.state);
|
|
20720
20755
|
}
|
|
20721
|
-
writeLine(ctx.io.stdout, `\u2713 Created goal ${g.goalId ?? g.id ?? ""} (state ${g.state ?? "active"}, check ${g.completion?.check ??
|
|
20756
|
+
writeLine(ctx.io.stdout, `\u2713 Created goal ${g.goalId ?? g.id ?? ""} (state ${g.state ?? "active"}, check ${g.completion?.check ?? "verifier"}).`);
|
|
20722
20757
|
return exitForState2(g.state);
|
|
20723
20758
|
}
|
|
20724
20759
|
async function runGoalsTransition(ctx, argv, verb) {
|
|
@@ -29786,7 +29821,7 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29786
29821
|
words: ["draft"],
|
|
29787
29822
|
method: "POST",
|
|
29788
29823
|
path: `${C3}/draft`,
|
|
29789
|
-
summary: "Draft (
|
|
29824
|
+
summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
|
|
29790
29825
|
body: [
|
|
29791
29826
|
{ flag: "--goal", key: "goal", required: true },
|
|
29792
29827
|
{ flag: "--allowed", key: "allowed", type: "csv" },
|
|
@@ -29796,12 +29831,16 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29796
29831
|
{ flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
|
|
29797
29832
|
]
|
|
29798
29833
|
},
|
|
29799
|
-
{ words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
|
|
29800
|
-
{ words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
|
|
29801
29834
|
{ words: ["reckoning"], method: "GET", path: `${C3}/reckoning`, summary: "What runs did vs. the mandate: used / blocked tools, within-mandate verdict (null before any stamped run)." }
|
|
29802
29835
|
];
|
|
29803
29836
|
var INTENT_LEDGER_HELP = `Usage:
|
|
29804
29837
|
${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
|
|
29838
|
+
Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
|
|
29839
|
+
openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
|
|
29840
|
+
openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
|
|
29841
|
+
Prints the contract; with --yes and the printed --digest, approves (or
|
|
29842
|
+
rejects/revokes) exactly that version. POST ${C3}/approve|reject
|
|
29843
|
+
|
|
29805
29844
|
Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
|
|
29806
29845
|
only records your intent and reads the host's reckoning.
|
|
29807
29846
|
|
|
@@ -29809,10 +29848,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
|
|
|
29809
29848
|
|
|
29810
29849
|
Examples:
|
|
29811
29850
|
openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
|
|
29812
|
-
openwop intent-ledger approve conv_1
|
|
29851
|
+
openwop intent-ledger approve conv_1 # prints the contract + its digest
|
|
29852
|
+
openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
|
|
29813
29853
|
openwop intent-ledger reckoning conv_1 --json
|
|
29814
29854
|
`;
|
|
29855
|
+
function contractLines(l) {
|
|
29856
|
+
const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
|
|
29857
|
+
return [
|
|
29858
|
+
`Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
|
|
29859
|
+
` goal: ${l.goal ?? ""}`,
|
|
29860
|
+
` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
|
|
29861
|
+
` forbidden: ${list(l.forbidden)}`,
|
|
29862
|
+
` needs approval: ${list(l.requireApproval)}`,
|
|
29863
|
+
` success criteria: ${list(l.successCriteria)}`,
|
|
29864
|
+
...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
|
|
29865
|
+
...l.digest ? [` digest: ${l.digest}`] : []
|
|
29866
|
+
];
|
|
29867
|
+
}
|
|
29868
|
+
async function decide2(ctx, verb, argv) {
|
|
29869
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
|
|
29870
|
+
if (options.help || options.h) {
|
|
29871
|
+
write(ctx.io.stdout, INTENT_LEDGER_HELP);
|
|
29872
|
+
return 0;
|
|
29873
|
+
}
|
|
29874
|
+
const conversationId = positionals[0];
|
|
29875
|
+
if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
|
|
29876
|
+
const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
|
|
29877
|
+
try {
|
|
29878
|
+
const got = await requestJson(ctx, path);
|
|
29879
|
+
const ledger = got.body?.ledger ?? null;
|
|
29880
|
+
if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
|
|
29881
|
+
if (!options.yes && !options.y) {
|
|
29882
|
+
if (options.json) writeJson(ctx.io.stdout, got.body);
|
|
29883
|
+
else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
|
|
29884
|
+
writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
|
|
29885
|
+
return 2;
|
|
29886
|
+
}
|
|
29887
|
+
if (ledger.digest) {
|
|
29888
|
+
if (!options.digest) {
|
|
29889
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29890
|
+
throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
|
|
29891
|
+
}
|
|
29892
|
+
if (options.digest !== ledger.digest) {
|
|
29893
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29894
|
+
throw new CliError(`The mission changed since you reviewed it (digest ${options.digest} \u2192 ${ledger.digest}). Review the version above and re-run with its digest.`, 2);
|
|
29895
|
+
}
|
|
29896
|
+
}
|
|
29897
|
+
const body = {};
|
|
29898
|
+
if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
|
|
29899
|
+
if (ledger.revision !== void 0) body.revision = ledger.revision;
|
|
29900
|
+
if (ledger.digest !== void 0) body.digest = ledger.digest;
|
|
29901
|
+
const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
|
|
29902
|
+
if (options.json) writeJson(ctx.io.stdout, res.body);
|
|
29903
|
+
else {
|
|
29904
|
+
const out = res.body?.ledger;
|
|
29905
|
+
writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
|
|
29906
|
+
}
|
|
29907
|
+
return 0;
|
|
29908
|
+
} catch (err) {
|
|
29909
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
29910
|
+
throw err;
|
|
29911
|
+
}
|
|
29912
|
+
}
|
|
29815
29913
|
async function runIntentLedger(ctx, argv) {
|
|
29914
|
+
if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
|
|
29816
29915
|
return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
|
|
29817
29916
|
}
|
|
29818
29917
|
|
|
@@ -31014,5 +31113,6 @@ export {
|
|
|
31014
31113
|
streamBaseFrom,
|
|
31015
31114
|
streamRunEvents,
|
|
31016
31115
|
submitTurn,
|
|
31017
|
-
summarizeCapabilities
|
|
31116
|
+
summarizeCapabilities,
|
|
31117
|
+
v2TemplatePattern
|
|
31018
31118
|
};
|
package/dist/openwop.js
CHANGED
|
@@ -579,7 +579,7 @@ function errText(err) {
|
|
|
579
579
|
// package.json
|
|
580
580
|
var package_default = {
|
|
581
581
|
name: "@openwop/cli",
|
|
582
|
-
version: "1.
|
|
582
|
+
version: "1.8.0",
|
|
583
583
|
description: "OpenWOP CLI for operating the workflow-engine demo app and OpenWOP-compatible hosts.",
|
|
584
584
|
license: "Apache-2.0",
|
|
585
585
|
type: "module",
|
|
@@ -856,21 +856,31 @@ function mergeConfig(existing, next) {
|
|
|
856
856
|
function getByPath(obj, path) {
|
|
857
857
|
return path.split(".").reduce((acc, key) => acc == null ? acc : acc[key], obj);
|
|
858
858
|
}
|
|
859
|
-
|
|
859
|
+
var UNSAFE_PATH_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
860
|
+
function safePathParts(path) {
|
|
860
861
|
const parts = path.split(".");
|
|
862
|
+
for (const part of parts) {
|
|
863
|
+
if (UNSAFE_PATH_SEGMENTS.has(part)) throw new CliError(`Config key segment '${part}' is not allowed: ${path}`);
|
|
864
|
+
}
|
|
865
|
+
return parts;
|
|
866
|
+
}
|
|
867
|
+
function setByPath(obj, path, value) {
|
|
868
|
+
const parts = safePathParts(path);
|
|
861
869
|
let cur = obj;
|
|
862
870
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
863
|
-
|
|
864
|
-
cur
|
|
871
|
+
const key = parts[i];
|
|
872
|
+
if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
873
|
+
cur = cur[key];
|
|
865
874
|
}
|
|
866
875
|
cur[parts[parts.length - 1]] = value;
|
|
867
876
|
}
|
|
868
877
|
function unsetByPath(obj, path) {
|
|
869
|
-
const parts = path
|
|
878
|
+
const parts = safePathParts(path);
|
|
870
879
|
let cur = obj;
|
|
871
880
|
for (let i = 0; i < parts.length - 1; i++) {
|
|
872
|
-
|
|
873
|
-
cur
|
|
881
|
+
const key = parts[i];
|
|
882
|
+
if (!Object.hasOwn(cur, key) || typeof cur[key] !== "object" || cur[key] === null) return;
|
|
883
|
+
cur = cur[key];
|
|
874
884
|
}
|
|
875
885
|
delete cur[parts[parts.length - 1]];
|
|
876
886
|
}
|
|
@@ -1097,12 +1107,22 @@ function decodeSegment(segment) {
|
|
|
1097
1107
|
return segment;
|
|
1098
1108
|
}
|
|
1099
1109
|
}
|
|
1110
|
+
function splitRunSegment(bare) {
|
|
1111
|
+
const PREFIX = "/runs/";
|
|
1112
|
+
if (!bare.startsWith(PREFIX)) return null;
|
|
1113
|
+
let end = PREFIX.length;
|
|
1114
|
+
while (end < bare.length && bare[end] !== "/" && bare[end] !== ":") end++;
|
|
1115
|
+
if (end === PREFIX.length) return null;
|
|
1116
|
+
const rest = bare.slice(end);
|
|
1117
|
+
if (/[\n\r\u2028\u2029]/.test(rest)) return null;
|
|
1118
|
+
return [bare.slice(PREFIX.length, end), rest];
|
|
1119
|
+
}
|
|
1100
1120
|
function projectRunIdsInPath(path) {
|
|
1101
1121
|
const q = path.indexOf("?");
|
|
1102
1122
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
1103
1123
|
let query = q === -1 ? "" : path.slice(q);
|
|
1104
|
-
const m =
|
|
1105
|
-
const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[
|
|
1124
|
+
const m = splitRunSegment(bare);
|
|
1125
|
+
const projectedBare = m ? `/runs/${projectTenantBoundId(decodeSegment(m[0]))}${m[1]}` : bare;
|
|
1106
1126
|
if (m && query) {
|
|
1107
1127
|
query = query.replace(/([?&]against=)([^&]*)/, (_all, key, value) => `${key}${projectTenantBoundId(decodeSegment(value))}`);
|
|
1108
1128
|
}
|
|
@@ -1167,9 +1187,11 @@ function v2SeamTwin(path) {
|
|
|
1167
1187
|
}
|
|
1168
1188
|
return null;
|
|
1169
1189
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
)
|
|
1190
|
+
function v2TemplatePattern(template) {
|
|
1191
|
+
const literals = template.split(/\{[A-Za-z]+\}/);
|
|
1192
|
+
return `^${literals.map((l) => l.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`;
|
|
1193
|
+
}
|
|
1194
|
+
var V2_MATCHERS = V2_PATH_TEMPLATES.map((t) => new RegExp(v2TemplatePattern(t)));
|
|
1173
1195
|
function v2Twin(path) {
|
|
1174
1196
|
const q = path.indexOf("?");
|
|
1175
1197
|
const bare = q === -1 ? path : path.slice(0, q);
|
|
@@ -1275,7 +1297,10 @@ function explicitStreamBase(raw) {
|
|
|
1275
1297
|
if (url.username !== "" || url.password !== "") {
|
|
1276
1298
|
throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
|
|
1277
1299
|
}
|
|
1278
|
-
|
|
1300
|
+
const base16 = raw.trim();
|
|
1301
|
+
let end = base16.length;
|
|
1302
|
+
while (end > 0 && base16[end - 1] === "/") end--;
|
|
1303
|
+
return base16.slice(0, end);
|
|
1279
1304
|
}
|
|
1280
1305
|
function streamBaseFrom(extensions, baseUrl) {
|
|
1281
1306
|
if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
|
|
@@ -20476,7 +20501,7 @@ var GOALS_ROUTES = [
|
|
|
20476
20501
|
var GOALS_HELP = `Usage:
|
|
20477
20502
|
openwop goals list [--state <s>] [--json]
|
|
20478
20503
|
openwop goals get <goalId> [--json]
|
|
20479
|
-
openwop goals create --objective <text>
|
|
20504
|
+
openwop goals create --objective <text> --verifier-ref <ref> [--continuation schedule|manual]
|
|
20480
20505
|
[--max-iterations <n>] [--max-cost <usd>] [--timeout-ms <ms>] [--json]
|
|
20481
20506
|
openwop goals pause <goalId> [--json]
|
|
20482
20507
|
openwop goals resume <goalId> [--json]
|
|
@@ -20503,13 +20528,16 @@ Endpoints:
|
|
|
20503
20528
|
resume POST /v1/host/openwop-app/goals/{id}/resume
|
|
20504
20529
|
abandon POST /v1/host/openwop-app/goals/{id}/abandon
|
|
20505
20530
|
|
|
20506
|
-
--state <s> (list) Filter by state
|
|
20531
|
+
--state <s> (list) Filter by state: active, satisfied, escalated, abandoned, bound-exceeded.
|
|
20507
20532
|
--objective <text> (create) The goal objective (required).
|
|
20508
|
-
--
|
|
20509
|
-
|
|
20510
|
-
--
|
|
20533
|
+
--verifier-ref <ref> (create) completion.verifierRef \u2014 the verifier registered on the host that
|
|
20534
|
+
judges this goal (required; the host refuses a ref it has not registered).
|
|
20535
|
+
--judge verifier (create) completion.check \u2014 who decides completion. 'verifier' is the only
|
|
20536
|
+
judge a host advertising agents.goals.judge=verifier honours (the default).
|
|
20537
|
+
--continuation <mode> (create) continuation.mode \u2014 schedule | manual (default: manual).
|
|
20538
|
+
--max-iterations <n> (create) Bound: bounds.maxLoopIterations (positive integer).
|
|
20511
20539
|
--max-cost <usd> (create) Bound: bounds.maxCostUsd (accumulated USD).
|
|
20512
|
-
--timeout-ms <ms> (create) Bound: bounds.runTimeoutMs (wall-clock, milliseconds).
|
|
20540
|
+
--timeout-ms <ms> (create) Bound: bounds.runTimeoutMs (wall-clock, positive milliseconds).
|
|
20513
20541
|
--yes (abandon) Required to confirm.
|
|
20514
20542
|
--json Print the raw host response instead of the rendered view.
|
|
20515
20543
|
|
|
@@ -20519,13 +20547,14 @@ termination guarantee (bounds).
|
|
|
20519
20547
|
|
|
20520
20548
|
Exit codes (get/create/pause/resume reflect the goal's state, so scripts can gate
|
|
20521
20549
|
on the verdict):
|
|
20522
|
-
0 satisfied 3 escalated or still open (active
|
|
20550
|
+
0 satisfied 3 escalated or still open (active) 1 bound-exceeded/abandoned or error
|
|
20551
|
+
A paused goal is still 'active' (pause stops its continuation, not the goal).
|
|
20523
20552
|
\`list\` exits 0 on success.
|
|
20524
20553
|
|
|
20525
20554
|
Examples:
|
|
20526
20555
|
openwop goals list --state active
|
|
20527
20556
|
openwop goals get goal_123 --json
|
|
20528
|
-
openwop goals create --objective "Keep the triage backlog under 20" --
|
|
20557
|
+
openwop goals create --objective "Keep the triage backlog under 20" --verifier-ref <ref> --continuation schedule --max-iterations 50 --max-cost 5.00 --timeout-ms 600000
|
|
20529
20558
|
openwop goals pause goal_123
|
|
20530
20559
|
openwop goals abandon goal_123 --yes
|
|
20531
20560
|
`;
|
|
@@ -20671,18 +20700,24 @@ function renderGoal(ctx, g, fallbackId) {
|
|
|
20671
20700
|
async function runGoalsCreate(ctx, argv) {
|
|
20672
20701
|
const { options } = parseOptions(argv, {
|
|
20673
20702
|
bool: ["--help"],
|
|
20674
|
-
value: ["--objective", "--judge", "--continuation", "--max-iterations", "--max-cost", "--timeout-ms"]
|
|
20703
|
+
value: ["--objective", "--judge", "--verifier-ref", "--continuation", "--max-iterations", "--max-cost", "--timeout-ms"]
|
|
20675
20704
|
});
|
|
20676
20705
|
if (options.help || !options.objective) {
|
|
20677
|
-
write(ctx.io.stdout, "Usage: openwop goals create --objective <text>
|
|
20706
|
+
write(ctx.io.stdout, "Usage: openwop goals create --objective <text> --verifier-ref <ref> [--continuation schedule|manual] [--max-iterations <n>] [--max-cost <usd>] [--timeout-ms <ms>] [--json]\n");
|
|
20678
20707
|
return options.help ? 0 : 2;
|
|
20679
20708
|
}
|
|
20680
|
-
if (options.judge !== void 0 &&
|
|
20681
|
-
throw new CliError("--judge must be
|
|
20709
|
+
if (options.judge !== void 0 && options.judge !== "verifier") {
|
|
20710
|
+
throw new CliError("--judge must be: verifier", 2);
|
|
20711
|
+
}
|
|
20712
|
+
if (!options.verifierRef) {
|
|
20713
|
+
throw new CliError("goals create needs --verifier-ref <ref> \u2014 the verifier registered on the host that judges the goal", 2);
|
|
20714
|
+
}
|
|
20715
|
+
if (options.continuation !== void 0 && !["schedule", "manual"].includes(options.continuation)) {
|
|
20716
|
+
throw new CliError("--continuation must be one of: schedule, manual", 2);
|
|
20682
20717
|
}
|
|
20683
20718
|
const body = {
|
|
20684
20719
|
objective: options.objective,
|
|
20685
|
-
completion: { check: options.
|
|
20720
|
+
completion: { check: "verifier", verifierRef: options.verifierRef },
|
|
20686
20721
|
continuation: { mode: options.continuation ?? "manual" }
|
|
20687
20722
|
};
|
|
20688
20723
|
const bounds = {};
|
|
@@ -20698,7 +20733,7 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20698
20733
|
}
|
|
20699
20734
|
if (options.timeoutMs !== void 0) {
|
|
20700
20735
|
const t = Number(options.timeoutMs);
|
|
20701
|
-
if (!Number.isInteger(t) || t
|
|
20736
|
+
if (!Number.isInteger(t) || t <= 0) throw new CliError("--timeout-ms must be a positive integer (milliseconds)", 2);
|
|
20702
20737
|
bounds.runTimeoutMs = t;
|
|
20703
20738
|
}
|
|
20704
20739
|
if (Object.keys(bounds).length) body.bounds = bounds;
|
|
@@ -20707,8 +20742,8 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20707
20742
|
try {
|
|
20708
20743
|
res = await requestJson(ctx, "/v1/host/openwop-app/goals", { method: "POST", body });
|
|
20709
20744
|
} catch (err) {
|
|
20710
|
-
if (err instanceof HttpError && err.status === 422) {
|
|
20711
|
-
const detail2 = err.body?.message ?? "this host requires bounds on an active goal \u2014 pass --max-iterations, --max-cost, and/or --timeout-ms";
|
|
20745
|
+
if (err instanceof HttpError && (err.status === 422 || err.status === 400)) {
|
|
20746
|
+
const detail2 = err.body?.message ?? (err.status === 422 ? "this host requires bounds on an active goal \u2014 pass --max-iterations, --max-cost, and/or --timeout-ms" : "the host refused the goal as malformed");
|
|
20712
20747
|
throw new CliError(`goals: ${detail2}`, 1);
|
|
20713
20748
|
}
|
|
20714
20749
|
gate4045(err);
|
|
@@ -20718,7 +20753,7 @@ async function runGoalsCreate(ctx, argv) {
|
|
|
20718
20753
|
writeJson(ctx.io.stdout, g);
|
|
20719
20754
|
return exitForState2(g.state);
|
|
20720
20755
|
}
|
|
20721
|
-
writeLine(ctx.io.stdout, `\u2713 Created goal ${g.goalId ?? g.id ?? ""} (state ${g.state ?? "active"}, check ${g.completion?.check ??
|
|
20756
|
+
writeLine(ctx.io.stdout, `\u2713 Created goal ${g.goalId ?? g.id ?? ""} (state ${g.state ?? "active"}, check ${g.completion?.check ?? "verifier"}).`);
|
|
20722
20757
|
return exitForState2(g.state);
|
|
20723
20758
|
}
|
|
20724
20759
|
async function runGoalsTransition(ctx, argv, verb) {
|
|
@@ -29786,7 +29821,7 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29786
29821
|
words: ["draft"],
|
|
29787
29822
|
method: "POST",
|
|
29788
29823
|
path: `${C3}/draft`,
|
|
29789
|
-
summary: "Draft (
|
|
29824
|
+
summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
|
|
29790
29825
|
body: [
|
|
29791
29826
|
{ flag: "--goal", key: "goal", required: true },
|
|
29792
29827
|
{ flag: "--allowed", key: "allowed", type: "csv" },
|
|
@@ -29796,12 +29831,16 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29796
29831
|
{ flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
|
|
29797
29832
|
]
|
|
29798
29833
|
},
|
|
29799
|
-
{ words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
|
|
29800
|
-
{ words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
|
|
29801
29834
|
{ words: ["reckoning"], method: "GET", path: `${C3}/reckoning`, summary: "What runs did vs. the mandate: used / blocked tools, within-mandate verdict (null before any stamped run)." }
|
|
29802
29835
|
];
|
|
29803
29836
|
var INTENT_LEDGER_HELP = `Usage:
|
|
29804
29837
|
${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
|
|
29838
|
+
Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
|
|
29839
|
+
openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
|
|
29840
|
+
openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
|
|
29841
|
+
Prints the contract; with --yes and the printed --digest, approves (or
|
|
29842
|
+
rejects/revokes) exactly that version. POST ${C3}/approve|reject
|
|
29843
|
+
|
|
29805
29844
|
Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
|
|
29806
29845
|
only records your intent and reads the host's reckoning.
|
|
29807
29846
|
|
|
@@ -29809,10 +29848,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
|
|
|
29809
29848
|
|
|
29810
29849
|
Examples:
|
|
29811
29850
|
openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
|
|
29812
|
-
openwop intent-ledger approve conv_1
|
|
29851
|
+
openwop intent-ledger approve conv_1 # prints the contract + its digest
|
|
29852
|
+
openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
|
|
29813
29853
|
openwop intent-ledger reckoning conv_1 --json
|
|
29814
29854
|
`;
|
|
29855
|
+
function contractLines(l) {
|
|
29856
|
+
const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
|
|
29857
|
+
return [
|
|
29858
|
+
`Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
|
|
29859
|
+
` goal: ${l.goal ?? ""}`,
|
|
29860
|
+
` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
|
|
29861
|
+
` forbidden: ${list(l.forbidden)}`,
|
|
29862
|
+
` needs approval: ${list(l.requireApproval)}`,
|
|
29863
|
+
` success criteria: ${list(l.successCriteria)}`,
|
|
29864
|
+
...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
|
|
29865
|
+
...l.digest ? [` digest: ${l.digest}`] : []
|
|
29866
|
+
];
|
|
29867
|
+
}
|
|
29868
|
+
async function decide2(ctx, verb, argv) {
|
|
29869
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
|
|
29870
|
+
if (options.help || options.h) {
|
|
29871
|
+
write(ctx.io.stdout, INTENT_LEDGER_HELP);
|
|
29872
|
+
return 0;
|
|
29873
|
+
}
|
|
29874
|
+
const conversationId = positionals[0];
|
|
29875
|
+
if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
|
|
29876
|
+
const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
|
|
29877
|
+
try {
|
|
29878
|
+
const got = await requestJson(ctx, path);
|
|
29879
|
+
const ledger = got.body?.ledger ?? null;
|
|
29880
|
+
if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
|
|
29881
|
+
if (!options.yes && !options.y) {
|
|
29882
|
+
if (options.json) writeJson(ctx.io.stdout, got.body);
|
|
29883
|
+
else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
|
|
29884
|
+
writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
|
|
29885
|
+
return 2;
|
|
29886
|
+
}
|
|
29887
|
+
if (ledger.digest) {
|
|
29888
|
+
if (!options.digest) {
|
|
29889
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29890
|
+
throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
|
|
29891
|
+
}
|
|
29892
|
+
if (options.digest !== ledger.digest) {
|
|
29893
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29894
|
+
throw new CliError(`The mission changed since you reviewed it (digest ${options.digest} \u2192 ${ledger.digest}). Review the version above and re-run with its digest.`, 2);
|
|
29895
|
+
}
|
|
29896
|
+
}
|
|
29897
|
+
const body = {};
|
|
29898
|
+
if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
|
|
29899
|
+
if (ledger.revision !== void 0) body.revision = ledger.revision;
|
|
29900
|
+
if (ledger.digest !== void 0) body.digest = ledger.digest;
|
|
29901
|
+
const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
|
|
29902
|
+
if (options.json) writeJson(ctx.io.stdout, res.body);
|
|
29903
|
+
else {
|
|
29904
|
+
const out = res.body?.ledger;
|
|
29905
|
+
writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
|
|
29906
|
+
}
|
|
29907
|
+
return 0;
|
|
29908
|
+
} catch (err) {
|
|
29909
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
29910
|
+
throw err;
|
|
29911
|
+
}
|
|
29912
|
+
}
|
|
29815
29913
|
async function runIntentLedger(ctx, argv) {
|
|
29914
|
+
if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
|
|
29816
29915
|
return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
|
|
29817
29916
|
}
|
|
29818
29917
|
|