@openwop/cli 1.5.0 → 1.7.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/dist/cli.js +164 -28
- package/dist/openwop.js +161 -27
- package/package.json +1 -1
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.7.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);
|
|
@@ -1209,6 +1231,14 @@ function hostRootFor(path, roots) {
|
|
|
1209
1231
|
}
|
|
1210
1232
|
return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
|
|
1211
1233
|
}
|
|
1234
|
+
function hostSurfaceAdvertised(discovery, v1Prefix) {
|
|
1235
|
+
if (!discovery || typeof discovery !== "object") return false;
|
|
1236
|
+
const { paths, extensions } = discovery;
|
|
1237
|
+
if (paths !== null && typeof paths === "object") {
|
|
1238
|
+
return Object.keys(paths).some((p) => p.startsWith(v1Prefix));
|
|
1239
|
+
}
|
|
1240
|
+
return hostRootFor(v1Prefix, hostRootsFrom(extensions)) !== null;
|
|
1241
|
+
}
|
|
1212
1242
|
async function negotiateMajor(ctx) {
|
|
1213
1243
|
if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
|
|
1214
1244
|
const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
|
|
@@ -1267,7 +1297,10 @@ function explicitStreamBase(raw) {
|
|
|
1267
1297
|
if (url.username !== "" || url.password !== "") {
|
|
1268
1298
|
throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
|
|
1269
1299
|
}
|
|
1270
|
-
|
|
1300
|
+
const base16 = raw.trim();
|
|
1301
|
+
let end = base16.length;
|
|
1302
|
+
while (end > 0 && base16[end - 1] === "/") end--;
|
|
1303
|
+
return base16.slice(0, end);
|
|
1271
1304
|
}
|
|
1272
1305
|
function streamBaseFrom(extensions, baseUrl) {
|
|
1273
1306
|
if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
|
|
@@ -16898,8 +16931,8 @@ var APPROVALS_ROUTES = [
|
|
|
16898
16931
|
var APPROVALS_HELP = `Usage:
|
|
16899
16932
|
openwop approvals list [--status pending|approved|rejected] [--json]
|
|
16900
16933
|
openwop approvals get <approvalId> [--json]
|
|
16901
|
-
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16902
|
-
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16934
|
+
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16935
|
+
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16903
16936
|
openwop approvals sla-policy [--json]
|
|
16904
16937
|
openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
|
|
16905
16938
|
openwop approvals email-pref [--json]
|
|
@@ -16937,6 +16970,11 @@ Endpoints:
|
|
|
16937
16970
|
--acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
|
|
16938
16971
|
--expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
|
|
16939
16972
|
the host refuses the decision if the proposal changed since).
|
|
16973
|
+
--content-hash <h> (claim/reject) The contentHash of the assistant-action card you reviewed
|
|
16974
|
+
(\`approvals get\` prints it under the card). REQUIRED to claim an
|
|
16975
|
+
assistant action (ADR 0862): the host refuses a claim without it (exit 2),
|
|
16976
|
+
and refuses one whose action changed since you read it (exit 1; re-read
|
|
16977
|
+
it with \`approvals get\` and review the new card).
|
|
16940
16978
|
--status <s> (list) Filter the queue: pending | approved | rejected (default: all).
|
|
16941
16979
|
--note <text> (claim/reject) Optional human note recorded with the decision.
|
|
16942
16980
|
--json Print the raw host response instead of the rendered view.
|
|
@@ -16953,6 +16991,8 @@ Examples:
|
|
|
16953
16991
|
openwop approvals list --status pending
|
|
16954
16992
|
openwop approvals get appr_123 --json
|
|
16955
16993
|
openwop approvals claim appr_123 --note "LGTM, ship it"
|
|
16994
|
+
openwop approvals get appr_456 # an assistant action: read the card
|
|
16995
|
+
openwop approvals claim appr_456 --content-hash 9f2c\u2026 # \u2026then approve exactly what you read
|
|
16956
16996
|
openwop approvals reject appr_123 --note "out of policy"
|
|
16957
16997
|
|
|
16958
16998
|
Teams approval delivery:
|
|
@@ -16999,8 +17039,7 @@ function exitForStatus(status) {
|
|
|
16999
17039
|
async function ensureApprovalsAdvertised(ctx) {
|
|
17000
17040
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17001
17041
|
if (!wk.ok) return;
|
|
17002
|
-
const
|
|
17003
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/approvals"));
|
|
17042
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/approvals");
|
|
17004
17043
|
if (!advertised) {
|
|
17005
17044
|
throw new CliError(
|
|
17006
17045
|
"approvals: this host does not advertise the approval-inbox surface (/v1/host/openwop-app/approvals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -17085,12 +17124,31 @@ async function runApprovalsGet(ctx, argv) {
|
|
|
17085
17124
|
if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
|
|
17086
17125
|
if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
|
|
17087
17126
|
if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
|
|
17127
|
+
if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
|
|
17088
17128
|
return exitForStatus(approval.status);
|
|
17089
17129
|
}
|
|
17130
|
+
function renderActionCard(ctx, action) {
|
|
17131
|
+
writeLine(ctx.io.stdout, "action:");
|
|
17132
|
+
if (action.actionId) writeLine(ctx.io.stdout, ` actionId: ${action.actionId}`);
|
|
17133
|
+
if (action.kind) writeLine(ctx.io.stdout, ` kind: ${action.kind}`);
|
|
17134
|
+
if (action.riskLevel) writeLine(ctx.io.stdout, ` riskLevel: ${action.riskLevel}`);
|
|
17135
|
+
if (action.reason) writeLine(ctx.io.stdout, ` reason: ${action.reason}`);
|
|
17136
|
+
if (typeof action.draft === "string" && action.draft.length) writeLine(ctx.io.stdout, ` draft: ${action.draft}`);
|
|
17137
|
+
if (action.payload && typeof action.payload === "object") {
|
|
17138
|
+
for (const [k, v] of Object.entries(action.payload)) {
|
|
17139
|
+
writeLine(ctx.io.stdout, ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
17140
|
+
}
|
|
17141
|
+
}
|
|
17142
|
+
if (action.contentHash) writeLine(ctx.io.stdout, ` contentHash: ${action.contentHash} (claim with --content-hash ${action.contentHash})`);
|
|
17143
|
+
}
|
|
17144
|
+
function detailsOf(body) {
|
|
17145
|
+
const d = body && typeof body === "object" ? body.details : void 0;
|
|
17146
|
+
return d && typeof d === "object" ? d : {};
|
|
17147
|
+
}
|
|
17090
17148
|
async function runApprovalsResolve(ctx, argv, verb) {
|
|
17091
|
-
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash"] });
|
|
17149
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash", "--content-hash"] });
|
|
17092
17150
|
if (options.help || positionals.length !== 1) {
|
|
17093
|
-
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
17151
|
+
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
17094
17152
|
`);
|
|
17095
17153
|
return options.help ? 0 : 2;
|
|
17096
17154
|
}
|
|
@@ -17100,11 +17158,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
|
|
|
17100
17158
|
if (options.note !== void 0) decision.note = options.note;
|
|
17101
17159
|
if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
|
|
17102
17160
|
if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
|
|
17161
|
+
if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
|
|
17103
17162
|
const body = Object.keys(decision).length ? decision : void 0;
|
|
17104
17163
|
let res;
|
|
17105
17164
|
try {
|
|
17106
17165
|
res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
|
|
17107
17166
|
} catch (err) {
|
|
17167
|
+
if (err instanceof HttpError && err.status === 409 && detailsOf(err.body).reason === "action_changed") {
|
|
17168
|
+
const now = detailsOf(err.body).contentHash;
|
|
17169
|
+
throw new CliError(
|
|
17170
|
+
`approvals: the action behind ${positionals[0]} changed since you reviewed it \u2014 nothing was ${verb === "claim" ? "approved" : "rejected"}. Re-read it with \`openwop approvals get ${positionals[0]}\`${now ? ` (its card now hashes to ${now})` : ""} and decide on what it says now.`,
|
|
17171
|
+
1
|
|
17172
|
+
);
|
|
17173
|
+
}
|
|
17174
|
+
if (err instanceof HttpError && err.status === 400 && detailsOf(err.body).reason === "content_hash_required") {
|
|
17175
|
+
throw new CliError(
|
|
17176
|
+
`approvals: ${positionals[0]} is an assistant action \u2014 read its card with \`openwop approvals get ${positionals[0]}\`, then pass the contentHash it shows: --content-hash <hash>.`,
|
|
17177
|
+
2
|
|
17178
|
+
);
|
|
17179
|
+
}
|
|
17108
17180
|
if (err instanceof HttpError && err.status === 409) {
|
|
17109
17181
|
const status = err.body?.status;
|
|
17110
17182
|
throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
|
|
@@ -17986,8 +18058,7 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17986
18058
|
async function ensureAdvertised(ctx) {
|
|
17987
18059
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17988
18060
|
if (!wk.ok) return;
|
|
17989
|
-
const
|
|
17990
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/connections"));
|
|
18061
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/connections");
|
|
17991
18062
|
if (!advertised) {
|
|
17992
18063
|
throw new CliError(
|
|
17993
18064
|
"connections: this host does not advertise the connections surface (/v1/host/openwop-app/connections is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -19396,8 +19467,7 @@ Run \`openwop workforces --help\` for usage.`);
|
|
|
19396
19467
|
async function ensureAdvertised2(ctx) {
|
|
19397
19468
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
19398
19469
|
if (!wk.ok) return;
|
|
19399
|
-
const
|
|
19400
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/workforces"));
|
|
19470
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/workforces");
|
|
19401
19471
|
if (!advertised) {
|
|
19402
19472
|
throw new CliError(
|
|
19403
19473
|
"workforces: this host does not advertise the workforces surface (/v1/host/openwop-app/workforces is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -29741,7 +29811,7 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29741
29811
|
words: ["draft"],
|
|
29742
29812
|
method: "POST",
|
|
29743
29813
|
path: `${C3}/draft`,
|
|
29744
|
-
summary: "Draft (
|
|
29814
|
+
summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
|
|
29745
29815
|
body: [
|
|
29746
29816
|
{ flag: "--goal", key: "goal", required: true },
|
|
29747
29817
|
{ flag: "--allowed", key: "allowed", type: "csv" },
|
|
@@ -29751,12 +29821,16 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29751
29821
|
{ flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
|
|
29752
29822
|
]
|
|
29753
29823
|
},
|
|
29754
|
-
{ words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
|
|
29755
|
-
{ words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
|
|
29756
29824
|
{ 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)." }
|
|
29757
29825
|
];
|
|
29758
29826
|
var INTENT_LEDGER_HELP = `Usage:
|
|
29759
29827
|
${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
|
|
29828
|
+
Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
|
|
29829
|
+
openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
|
|
29830
|
+
openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
|
|
29831
|
+
Prints the contract; with --yes and the printed --digest, approves (or
|
|
29832
|
+
rejects/revokes) exactly that version. POST ${C3}/approve|reject
|
|
29833
|
+
|
|
29760
29834
|
Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
|
|
29761
29835
|
only records your intent and reads the host's reckoning.
|
|
29762
29836
|
|
|
@@ -29764,10 +29838,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
|
|
|
29764
29838
|
|
|
29765
29839
|
Examples:
|
|
29766
29840
|
openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
|
|
29767
|
-
openwop intent-ledger approve conv_1
|
|
29841
|
+
openwop intent-ledger approve conv_1 # prints the contract + its digest
|
|
29842
|
+
openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
|
|
29768
29843
|
openwop intent-ledger reckoning conv_1 --json
|
|
29769
29844
|
`;
|
|
29845
|
+
function contractLines(l) {
|
|
29846
|
+
const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
|
|
29847
|
+
return [
|
|
29848
|
+
`Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
|
|
29849
|
+
` goal: ${l.goal ?? ""}`,
|
|
29850
|
+
` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
|
|
29851
|
+
` forbidden: ${list(l.forbidden)}`,
|
|
29852
|
+
` needs approval: ${list(l.requireApproval)}`,
|
|
29853
|
+
` success criteria: ${list(l.successCriteria)}`,
|
|
29854
|
+
...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
|
|
29855
|
+
...l.digest ? [` digest: ${l.digest}`] : []
|
|
29856
|
+
];
|
|
29857
|
+
}
|
|
29858
|
+
async function decide2(ctx, verb, argv) {
|
|
29859
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
|
|
29860
|
+
if (options.help || options.h) {
|
|
29861
|
+
write(ctx.io.stdout, INTENT_LEDGER_HELP);
|
|
29862
|
+
return 0;
|
|
29863
|
+
}
|
|
29864
|
+
const conversationId = positionals[0];
|
|
29865
|
+
if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
|
|
29866
|
+
const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
|
|
29867
|
+
try {
|
|
29868
|
+
const got = await requestJson(ctx, path);
|
|
29869
|
+
const ledger = got.body?.ledger ?? null;
|
|
29870
|
+
if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
|
|
29871
|
+
if (!options.yes && !options.y) {
|
|
29872
|
+
if (options.json) writeJson(ctx.io.stdout, got.body);
|
|
29873
|
+
else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
|
|
29874
|
+
writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
|
|
29875
|
+
return 2;
|
|
29876
|
+
}
|
|
29877
|
+
if (ledger.digest) {
|
|
29878
|
+
if (!options.digest) {
|
|
29879
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29880
|
+
throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
|
|
29881
|
+
}
|
|
29882
|
+
if (options.digest !== ledger.digest) {
|
|
29883
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29884
|
+
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);
|
|
29885
|
+
}
|
|
29886
|
+
}
|
|
29887
|
+
const body = {};
|
|
29888
|
+
if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
|
|
29889
|
+
if (ledger.revision !== void 0) body.revision = ledger.revision;
|
|
29890
|
+
if (ledger.digest !== void 0) body.digest = ledger.digest;
|
|
29891
|
+
const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
|
|
29892
|
+
if (options.json) writeJson(ctx.io.stdout, res.body);
|
|
29893
|
+
else {
|
|
29894
|
+
const out = res.body?.ledger;
|
|
29895
|
+
writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
|
|
29896
|
+
}
|
|
29897
|
+
return 0;
|
|
29898
|
+
} catch (err) {
|
|
29899
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
29900
|
+
throw err;
|
|
29901
|
+
}
|
|
29902
|
+
}
|
|
29770
29903
|
async function runIntentLedger(ctx, argv) {
|
|
29904
|
+
if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
|
|
29771
29905
|
return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
|
|
29772
29906
|
}
|
|
29773
29907
|
|
|
@@ -30944,6 +31078,7 @@ export {
|
|
|
30944
31078
|
getChannelPlugin,
|
|
30945
31079
|
hostRootFor,
|
|
30946
31080
|
hostRootsFrom,
|
|
31081
|
+
hostSurfaceAdvertised,
|
|
30947
31082
|
idempotencyHeaders,
|
|
30948
31083
|
isNoCredentialChallenge,
|
|
30949
31084
|
isTerminalRunEvent,
|
|
@@ -30968,5 +31103,6 @@ export {
|
|
|
30968
31103
|
streamBaseFrom,
|
|
30969
31104
|
streamRunEvents,
|
|
30970
31105
|
submitTurn,
|
|
30971
|
-
summarizeCapabilities
|
|
31106
|
+
summarizeCapabilities,
|
|
31107
|
+
v2TemplatePattern
|
|
30972
31108
|
};
|
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.7.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);
|
|
@@ -1209,6 +1231,14 @@ function hostRootFor(path, roots) {
|
|
|
1209
1231
|
}
|
|
1210
1232
|
return best === null ? null : `${roots[best]}${path.slice(best.length)}`;
|
|
1211
1233
|
}
|
|
1234
|
+
function hostSurfaceAdvertised(discovery, v1Prefix) {
|
|
1235
|
+
if (!discovery || typeof discovery !== "object") return false;
|
|
1236
|
+
const { paths, extensions } = discovery;
|
|
1237
|
+
if (paths !== null && typeof paths === "object") {
|
|
1238
|
+
return Object.keys(paths).some((p) => p.startsWith(v1Prefix));
|
|
1239
|
+
}
|
|
1240
|
+
return hostRootFor(v1Prefix, hostRootsFrom(extensions)) !== null;
|
|
1241
|
+
}
|
|
1212
1242
|
async function negotiateMajor(ctx) {
|
|
1213
1243
|
if (ctx.protocolMajor !== void 0) return ctx.protocolMajor;
|
|
1214
1244
|
const pinned = ctx.env?.OPENWOP_PROTOCOL_MAJOR;
|
|
@@ -1267,7 +1297,10 @@ function explicitStreamBase(raw) {
|
|
|
1267
1297
|
if (url.username !== "" || url.password !== "") {
|
|
1268
1298
|
throw new CliError("--stream-base-url / OPENWOP_STREAM_BASE_URL must not carry credentials (user:pass@); pass them with --api-key.");
|
|
1269
1299
|
}
|
|
1270
|
-
|
|
1300
|
+
const base16 = raw.trim();
|
|
1301
|
+
let end = base16.length;
|
|
1302
|
+
while (end > 0 && base16[end - 1] === "/") end--;
|
|
1303
|
+
return base16.slice(0, end);
|
|
1271
1304
|
}
|
|
1272
1305
|
function streamBaseFrom(extensions, baseUrl) {
|
|
1273
1306
|
if (!extensions || typeof extensions !== "object" || Array.isArray(extensions)) return void 0;
|
|
@@ -16898,8 +16931,8 @@ var APPROVALS_ROUTES = [
|
|
|
16898
16931
|
var APPROVALS_HELP = `Usage:
|
|
16899
16932
|
openwop approvals list [--status pending|approved|rejected] [--json]
|
|
16900
16933
|
openwop approvals get <approvalId> [--json]
|
|
16901
|
-
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16902
|
-
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
16934
|
+
openwop approvals claim <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16935
|
+
openwop approvals reject <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
16903
16936
|
openwop approvals sla-policy [--json]
|
|
16904
16937
|
openwop approvals sla-policy set (--enabled | --disabled) [--remind-after-ms n] [--escalate-after-ms n] [--expire-after-ms n] [--json]
|
|
16905
16938
|
openwop approvals email-pref [--json]
|
|
@@ -16937,6 +16970,11 @@ Endpoints:
|
|
|
16937
16970
|
--acted-for <s> (claim/reject) As a delegate covering several people, whose approval this is.
|
|
16938
16971
|
--expected-hash <h> (claim/reject) The definition hash you reviewed (composed-workflow proposals;
|
|
16939
16972
|
the host refuses the decision if the proposal changed since).
|
|
16973
|
+
--content-hash <h> (claim/reject) The contentHash of the assistant-action card you reviewed
|
|
16974
|
+
(\`approvals get\` prints it under the card). REQUIRED to claim an
|
|
16975
|
+
assistant action (ADR 0862): the host refuses a claim without it (exit 2),
|
|
16976
|
+
and refuses one whose action changed since you read it (exit 1; re-read
|
|
16977
|
+
it with \`approvals get\` and review the new card).
|
|
16940
16978
|
--status <s> (list) Filter the queue: pending | approved | rejected (default: all).
|
|
16941
16979
|
--note <text> (claim/reject) Optional human note recorded with the decision.
|
|
16942
16980
|
--json Print the raw host response instead of the rendered view.
|
|
@@ -16953,6 +16991,8 @@ Examples:
|
|
|
16953
16991
|
openwop approvals list --status pending
|
|
16954
16992
|
openwop approvals get appr_123 --json
|
|
16955
16993
|
openwop approvals claim appr_123 --note "LGTM, ship it"
|
|
16994
|
+
openwop approvals get appr_456 # an assistant action: read the card
|
|
16995
|
+
openwop approvals claim appr_456 --content-hash 9f2c\u2026 # \u2026then approve exactly what you read
|
|
16956
16996
|
openwop approvals reject appr_123 --note "out of policy"
|
|
16957
16997
|
|
|
16958
16998
|
Teams approval delivery:
|
|
@@ -16999,8 +17039,7 @@ function exitForStatus(status) {
|
|
|
16999
17039
|
async function ensureApprovalsAdvertised(ctx) {
|
|
17000
17040
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17001
17041
|
if (!wk.ok) return;
|
|
17002
|
-
const
|
|
17003
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/approvals"));
|
|
17042
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/approvals");
|
|
17004
17043
|
if (!advertised) {
|
|
17005
17044
|
throw new CliError(
|
|
17006
17045
|
"approvals: this host does not advertise the approval-inbox surface (/v1/host/openwop-app/approvals is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -17085,12 +17124,31 @@ async function runApprovalsGet(ctx, argv) {
|
|
|
17085
17124
|
if (approval.resolvedAt) writeLine(ctx.io.stdout, `resolvedAt: ${approval.resolvedAt}`);
|
|
17086
17125
|
if (approval.runId) writeLine(ctx.io.stdout, `runId: ${approval.runId}`);
|
|
17087
17126
|
if (approval.note) writeLine(ctx.io.stdout, `note: ${approval.note}`);
|
|
17127
|
+
if (approval.action && typeof approval.action === "object") renderActionCard(ctx, approval.action);
|
|
17088
17128
|
return exitForStatus(approval.status);
|
|
17089
17129
|
}
|
|
17130
|
+
function renderActionCard(ctx, action) {
|
|
17131
|
+
writeLine(ctx.io.stdout, "action:");
|
|
17132
|
+
if (action.actionId) writeLine(ctx.io.stdout, ` actionId: ${action.actionId}`);
|
|
17133
|
+
if (action.kind) writeLine(ctx.io.stdout, ` kind: ${action.kind}`);
|
|
17134
|
+
if (action.riskLevel) writeLine(ctx.io.stdout, ` riskLevel: ${action.riskLevel}`);
|
|
17135
|
+
if (action.reason) writeLine(ctx.io.stdout, ` reason: ${action.reason}`);
|
|
17136
|
+
if (typeof action.draft === "string" && action.draft.length) writeLine(ctx.io.stdout, ` draft: ${action.draft}`);
|
|
17137
|
+
if (action.payload && typeof action.payload === "object") {
|
|
17138
|
+
for (const [k, v] of Object.entries(action.payload)) {
|
|
17139
|
+
writeLine(ctx.io.stdout, ` ${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
17140
|
+
}
|
|
17141
|
+
}
|
|
17142
|
+
if (action.contentHash) writeLine(ctx.io.stdout, ` contentHash: ${action.contentHash} (claim with --content-hash ${action.contentHash})`);
|
|
17143
|
+
}
|
|
17144
|
+
function detailsOf(body) {
|
|
17145
|
+
const d = body && typeof body === "object" ? body.details : void 0;
|
|
17146
|
+
return d && typeof d === "object" ? d : {};
|
|
17147
|
+
}
|
|
17090
17148
|
async function runApprovalsResolve(ctx, argv, verb) {
|
|
17091
|
-
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash"] });
|
|
17149
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--help"], value: ["--note", "--acted-for", "--expected-hash", "--content-hash"] });
|
|
17092
17150
|
if (options.help || positionals.length !== 1) {
|
|
17093
|
-
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--json]
|
|
17151
|
+
write(ctx.io.stdout, `Usage: openwop approvals ${verb} <approvalId> [--note <text>] [--acted-for <subject>] [--expected-hash <h>] [--content-hash <h>] [--json]
|
|
17094
17152
|
`);
|
|
17095
17153
|
return options.help ? 0 : 2;
|
|
17096
17154
|
}
|
|
@@ -17100,11 +17158,25 @@ async function runApprovalsResolve(ctx, argv, verb) {
|
|
|
17100
17158
|
if (options.note !== void 0) decision.note = options.note;
|
|
17101
17159
|
if (options.actedFor !== void 0) decision.actedFor = options.actedFor;
|
|
17102
17160
|
if (options.expectedHash !== void 0) decision.expectedDefinitionHash = options.expectedHash;
|
|
17161
|
+
if (options.contentHash !== void 0) decision.expectedContentHash = options.contentHash;
|
|
17103
17162
|
const body = Object.keys(decision).length ? decision : void 0;
|
|
17104
17163
|
let res;
|
|
17105
17164
|
try {
|
|
17106
17165
|
res = await requestJson(ctx, path, { method: "POST", ...body !== void 0 ? { body } : {} });
|
|
17107
17166
|
} catch (err) {
|
|
17167
|
+
if (err instanceof HttpError && err.status === 409 && detailsOf(err.body).reason === "action_changed") {
|
|
17168
|
+
const now = detailsOf(err.body).contentHash;
|
|
17169
|
+
throw new CliError(
|
|
17170
|
+
`approvals: the action behind ${positionals[0]} changed since you reviewed it \u2014 nothing was ${verb === "claim" ? "approved" : "rejected"}. Re-read it with \`openwop approvals get ${positionals[0]}\`${now ? ` (its card now hashes to ${now})` : ""} and decide on what it says now.`,
|
|
17171
|
+
1
|
|
17172
|
+
);
|
|
17173
|
+
}
|
|
17174
|
+
if (err instanceof HttpError && err.status === 400 && detailsOf(err.body).reason === "content_hash_required") {
|
|
17175
|
+
throw new CliError(
|
|
17176
|
+
`approvals: ${positionals[0]} is an assistant action \u2014 read its card with \`openwop approvals get ${positionals[0]}\`, then pass the contentHash it shows: --content-hash <hash>.`,
|
|
17177
|
+
2
|
|
17178
|
+
);
|
|
17179
|
+
}
|
|
17108
17180
|
if (err instanceof HttpError && err.status === 409) {
|
|
17109
17181
|
const status = err.body?.status;
|
|
17110
17182
|
throw new CliError(`approvals: ${positionals[0]} is already ${status ?? "resolved"} \u2014 the host rejected a second decision.`, 1);
|
|
@@ -17986,8 +18058,7 @@ Run \`openwop connections --help\` for usage.`);
|
|
|
17986
18058
|
async function ensureAdvertised(ctx) {
|
|
17987
18059
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
17988
18060
|
if (!wk.ok) return;
|
|
17989
|
-
const
|
|
17990
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/connections"));
|
|
18061
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/connections");
|
|
17991
18062
|
if (!advertised) {
|
|
17992
18063
|
throw new CliError(
|
|
17993
18064
|
"connections: this host does not advertise the connections surface (/v1/host/openwop-app/connections is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -19396,8 +19467,7 @@ Run \`openwop workforces --help\` for usage.`);
|
|
|
19396
19467
|
async function ensureAdvertised2(ctx) {
|
|
19397
19468
|
const wk = await safeRequest(ctx, "/.well-known/openwop", { auth: false });
|
|
19398
19469
|
if (!wk.ok) return;
|
|
19399
|
-
const
|
|
19400
|
-
const advertised = paths !== null && typeof paths === "object" && Object.keys(paths).some((p) => p.startsWith("/v1/host/openwop-app/workforces"));
|
|
19470
|
+
const advertised = hostSurfaceAdvertised(wk.body, "/v1/host/openwop-app/workforces");
|
|
19401
19471
|
if (!advertised) {
|
|
19402
19472
|
throw new CliError(
|
|
19403
19473
|
"workforces: this host does not advertise the workforces surface (/v1/host/openwop-app/workforces is absent from /.well-known/openwop). The host is the authority \u2014 refusing to guess.",
|
|
@@ -29741,7 +29811,7 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29741
29811
|
words: ["draft"],
|
|
29742
29812
|
method: "POST",
|
|
29743
29813
|
path: `${C3}/draft`,
|
|
29744
|
-
summary: "Draft (
|
|
29814
|
+
summary: "Draft the ledger (replaces a draft; an approved mission must be rejected first): a new draft the user then approves.",
|
|
29745
29815
|
body: [
|
|
29746
29816
|
{ flag: "--goal", key: "goal", required: true },
|
|
29747
29817
|
{ flag: "--allowed", key: "allowed", type: "csv" },
|
|
@@ -29751,12 +29821,16 @@ var INTENT_LEDGER_ROUTES = [
|
|
|
29751
29821
|
{ flag: "--expires-at-rel-ms", key: "expiresAtRelMs", type: "number" }
|
|
29752
29822
|
]
|
|
29753
29823
|
},
|
|
29754
|
-
{ words: ["approve"], method: "POST", path: `${C3}/approve`, summary: "Approve the drafted ledger (you become approvedBy)." },
|
|
29755
|
-
{ words: ["reject"], method: "POST", path: `${C3}/reject`, summary: "Reject the drafted ledger." },
|
|
29756
29824
|
{ 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)." }
|
|
29757
29825
|
];
|
|
29758
29826
|
var INTENT_LEDGER_HELP = `Usage:
|
|
29759
29827
|
${routesHelp("intent-ledger", INTENT_LEDGER_ROUTES)}
|
|
29828
|
+
Decisions (read, review, then decide exactly what you reviewed \u2014 openwop-app ADR 0896):
|
|
29829
|
+
openwop intent-ledger approve <conversationId> [--yes --digest <digest>] [--json]
|
|
29830
|
+
openwop intent-ledger reject <conversationId> [--yes --digest <digest>] [--json]
|
|
29831
|
+
Prints the contract; with --yes and the printed --digest, approves (or
|
|
29832
|
+
rejects/revokes) exactly that version. POST ${C3}/approve|reject
|
|
29833
|
+
|
|
29760
29834
|
Intent ledger (ADR 0136). The host enforces the ledger at tool-call time; the CLI
|
|
29761
29835
|
only records your intent and reads the host's reckoning.
|
|
29762
29836
|
|
|
@@ -29764,10 +29838,70 @@ Exit codes: 0 ok \xB7 1 server error \xB7 2 usage / not found \xB7 4 not signed
|
|
|
29764
29838
|
|
|
29765
29839
|
Examples:
|
|
29766
29840
|
openwop intent-ledger draft conv_1 --goal "Book travel" --allowed travel.search,travel.book --require-approval travel.book
|
|
29767
|
-
openwop intent-ledger approve conv_1
|
|
29841
|
+
openwop intent-ledger approve conv_1 # prints the contract + its digest
|
|
29842
|
+
openwop intent-ledger approve conv_1 --yes --digest sha256:\u2026
|
|
29768
29843
|
openwop intent-ledger reckoning conv_1 --json
|
|
29769
29844
|
`;
|
|
29845
|
+
function contractLines(l) {
|
|
29846
|
+
const list = (xs) => xs && xs.length ? xs.join(", ") : "(none)";
|
|
29847
|
+
return [
|
|
29848
|
+
`Mission ${l.ledgerId ?? "?"} \u2014 ${l.status ?? "?"}${l.revision !== void 0 ? ` (revision ${l.revision})` : ""}`,
|
|
29849
|
+
` goal: ${l.goal ?? ""}`,
|
|
29850
|
+
` allowed: ${l.allowed && l.allowed.length ? l.allowed.join(", ") : "(none \u2014 the agent may talk but not act)"}`,
|
|
29851
|
+
` forbidden: ${list(l.forbidden)}`,
|
|
29852
|
+
` needs approval: ${list(l.requireApproval)}`,
|
|
29853
|
+
` success criteria: ${list(l.successCriteria)}`,
|
|
29854
|
+
...l.expiresAtRelMs !== void 0 ? [` ends: ${Math.round(l.expiresAtRelMs / 6e4)} min after the agent starts working under it`] : [],
|
|
29855
|
+
...l.digest ? [` digest: ${l.digest}`] : []
|
|
29856
|
+
];
|
|
29857
|
+
}
|
|
29858
|
+
async function decide2(ctx, verb, argv) {
|
|
29859
|
+
const { options, positionals } = parseOptions(argv, { bool: ["--yes", "-y", "--json", "--help", "-h"], value: ["--digest"] });
|
|
29860
|
+
if (options.help || options.h) {
|
|
29861
|
+
write(ctx.io.stdout, INTENT_LEDGER_HELP);
|
|
29862
|
+
return 0;
|
|
29863
|
+
}
|
|
29864
|
+
const conversationId = positionals[0];
|
|
29865
|
+
if (!conversationId || positionals.length > 1) throw new CliError(`Usage: openwop intent-ledger ${verb} <conversationId> [--yes --digest <digest>]`, 2);
|
|
29866
|
+
const path = C3.replace(":conversationId", encodeURIComponent(conversationId));
|
|
29867
|
+
try {
|
|
29868
|
+
const got = await requestJson(ctx, path);
|
|
29869
|
+
const ledger = got.body?.ledger ?? null;
|
|
29870
|
+
if (!ledger) throw new CliError(`No mission ledger on conversation ${conversationId}.`, 2);
|
|
29871
|
+
if (!options.yes && !options.y) {
|
|
29872
|
+
if (options.json) writeJson(ctx.io.stdout, got.body);
|
|
29873
|
+
else for (const line of contractLines(ledger)) writeLine(ctx.io.stdout, line);
|
|
29874
|
+
writeLine(ctx.io.stderr, `Review the contract above, then re-run: openwop intent-ledger ${verb} ${conversationId} --yes${ledger.digest ? ` --digest ${ledger.digest}` : ""}`);
|
|
29875
|
+
return 2;
|
|
29876
|
+
}
|
|
29877
|
+
if (ledger.digest) {
|
|
29878
|
+
if (!options.digest) {
|
|
29879
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29880
|
+
throw new CliError(`Pass --digest ${ledger.digest} to ${verb} exactly this contract.`, 2);
|
|
29881
|
+
}
|
|
29882
|
+
if (options.digest !== ledger.digest) {
|
|
29883
|
+
for (const line of contractLines(ledger)) writeLine(ctx.io.stderr, line);
|
|
29884
|
+
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);
|
|
29885
|
+
}
|
|
29886
|
+
}
|
|
29887
|
+
const body = {};
|
|
29888
|
+
if (ledger.ledgerId !== void 0) body.ledgerId = ledger.ledgerId;
|
|
29889
|
+
if (ledger.revision !== void 0) body.revision = ledger.revision;
|
|
29890
|
+
if (ledger.digest !== void 0) body.digest = ledger.digest;
|
|
29891
|
+
const res = await requestJson(ctx, `${path}/${verb}`, { method: "POST", body });
|
|
29892
|
+
if (options.json) writeJson(ctx.io.stdout, res.body);
|
|
29893
|
+
else {
|
|
29894
|
+
const out = res.body?.ledger;
|
|
29895
|
+
writeLine(ctx.io.stdout, `Mission ${out?.ledgerId ?? ledger.ledgerId ?? ""} ${out?.status ?? (verb === "approve" ? "approved" : "rejected")}.`);
|
|
29896
|
+
}
|
|
29897
|
+
return 0;
|
|
29898
|
+
} catch (err) {
|
|
29899
|
+
if (err instanceof HttpError) throw hostError(err);
|
|
29900
|
+
throw err;
|
|
29901
|
+
}
|
|
29902
|
+
}
|
|
29770
29903
|
async function runIntentLedger(ctx, argv) {
|
|
29904
|
+
if (argv[0] === "approve" || argv[0] === "reject") return decide2(ctx, argv[0], argv.slice(1));
|
|
29771
29905
|
return runRouteGroup(ctx, "intent-ledger", INTENT_LEDGER_HELP, INTENT_LEDGER_ROUTES, argv);
|
|
29772
29906
|
}
|
|
29773
29907
|
|