@divergeai/cli 0.1.25 → 0.1.27
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 +13 -3
- package/dist/diverge.mjs +132 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,8 +157,13 @@ The set is derived from the login, never chosen on the machine: who is
|
|
|
157
157
|
signed in, where they may act, and what they can run there. A skill's
|
|
158
158
|
identity is its organization and its name, so every organization's skills
|
|
159
159
|
sit side by side in one folder, each under the organization's prefix — its
|
|
160
|
-
|
|
161
|
-
|
|
160
|
+
handle, the same lowercase slug that prefixes its case numbers, or a slug of
|
|
161
|
+
its name when it chose none: `acme-consultar-inventario`,
|
|
162
|
+
`red-team-triage-cloud-run`. A folder longer than the 64 characters a
|
|
163
|
+
harness accepts as a skill name is cut and ended with a short stable hash,
|
|
164
|
+
and its SKILL.md names the skill; a served folder that is not a skill name is
|
|
165
|
+
skipped with a warning naming it, and the folder that skill was installed
|
|
166
|
+
under before stays. Each SKILL.md is rendered from the skill's
|
|
162
167
|
record on every install, named after its folder, and opens with the
|
|
163
168
|
organization its tools run in, so an agent passes `--organization-id` on
|
|
164
169
|
every call instead of depending on the machine's default organization.
|
|
@@ -249,12 +254,17 @@ reaches it through your bridge binding. See
|
|
|
249
254
|
|
|
250
255
|
```bash
|
|
251
256
|
diverge plugins validate manifest.json # dry run: every problem the platform would refuse, at once
|
|
252
|
-
diverge plugins publish manifest.json # forge_plugins:create in the login's organization
|
|
257
|
+
diverge plugins publish manifest.json # forge_plugins:create in the login's organization; exits 3 when it waits on a decision
|
|
253
258
|
diverge plugins list
|
|
254
259
|
diverge plugins retire <pluginId> # the descriptor and its tools go; installations stay
|
|
255
260
|
```
|
|
256
261
|
|
|
257
262
|
Tools are pinned to the manifest: a change is a new version published again.
|
|
263
|
+
A version that only reshapes or removes the tools already served is stored at
|
|
264
|
+
once. Anything else changes authority and is stored only once a person decides
|
|
265
|
+
it: `publish` raises it as `forge_publish_plugin`, prints the invocation and
|
|
266
|
+
its Compass link, and exits 3; read the outcome with
|
|
267
|
+
`diverge tools invocation <id>`.
|
|
258
268
|
After publishing, enable the plugin with `forge_set_plugin_state` (or the
|
|
259
269
|
Forge Routing page) and bind the runtime with a bridge binding whose
|
|
260
270
|
`runtimeType` equals the manifest's `runtime.type`; declare `networkPolicy`
|
package/dist/diverge.mjs
CHANGED
|
@@ -7556,7 +7556,7 @@ function tokenSource(env, baseUrl) {
|
|
|
7556
7556
|
}
|
|
7557
7557
|
|
|
7558
7558
|
// src/version.ts
|
|
7559
|
-
var VERSION = true ? "0.1.
|
|
7559
|
+
var VERSION = true ? "0.1.27" : "dev";
|
|
7560
7560
|
var USER_AGENT = `diverge/${VERSION}`;
|
|
7561
7561
|
|
|
7562
7562
|
// src/http.ts
|
|
@@ -8361,8 +8361,16 @@ function describeToolExecution(env = process.env) {
|
|
|
8361
8361
|
}
|
|
8362
8362
|
|
|
8363
8363
|
// src/tools/client.ts
|
|
8364
|
+
var AUTHORITY_CHANGE_CODE = "REMOTE_PLUGIN_AUTHORITY_CHANGE";
|
|
8364
8365
|
var INVOCATION_SET_TOOL = "functions_invocation_set";
|
|
8365
8366
|
var PENDING_INVOCATION_STATUSES = ["pending", "approved", "executing"];
|
|
8367
|
+
function parseJson(text2) {
|
|
8368
|
+
try {
|
|
8369
|
+
return JSON.parse(text2);
|
|
8370
|
+
} catch {
|
|
8371
|
+
return void 0;
|
|
8372
|
+
}
|
|
8373
|
+
}
|
|
8366
8374
|
function isPending(result) {
|
|
8367
8375
|
return !!result && typeof result === "object" && result.status === "pending_approval";
|
|
8368
8376
|
}
|
|
@@ -8451,7 +8459,7 @@ var ToolsClient = class {
|
|
|
8451
8459
|
if (typeof parsed.error === "string" && parsed.error) message = parsed.error;
|
|
8452
8460
|
} catch {
|
|
8453
8461
|
}
|
|
8454
|
-
if (response.status === 403 &&
|
|
8462
|
+
if (response.status === 403 && /^insufficient scope$/i.test(message.trim())) {
|
|
8455
8463
|
throw new CliError(`${response.status}: ${message}
|
|
8456
8464
|
The signed-in token is missing a required scope. Re-run: ${LOGIN_HINT}`);
|
|
8457
8465
|
}
|
|
@@ -8523,14 +8531,14 @@ var ToolsClient = class {
|
|
|
8523
8531
|
this.descriptorsUrl(),
|
|
8524
8532
|
{ targetOrganizationId: this.settings.organizationId, manifest, dryRun: options.dryRun },
|
|
8525
8533
|
(status2, text2) => {
|
|
8526
|
-
if (status2
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
if (Array.isArray(parsed.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8530
|
-
return { ok: false, details: [parsed.error ?? text2] };
|
|
8531
|
-
} catch {
|
|
8532
|
-
return { ok: false, details: [text2] };
|
|
8534
|
+
if (status2 === 403) {
|
|
8535
|
+
const parsed2 = parseJson(text2);
|
|
8536
|
+
return parsed2?.code === AUTHORITY_CHANGE_CODE && Array.isArray(parsed2.details) ? { ok: false, details: parsed2.details.map(String), authorityChange: true } : void 0;
|
|
8533
8537
|
}
|
|
8538
|
+
if (status2 !== 400) return void 0;
|
|
8539
|
+
const parsed = parseJson(text2);
|
|
8540
|
+
if (Array.isArray(parsed?.details)) return { ok: false, details: parsed.details.map(String) };
|
|
8541
|
+
return { ok: false, details: [parsed?.error ?? text2] };
|
|
8534
8542
|
}
|
|
8535
8543
|
).then((outcome) => "ok" in outcome ? outcome : { ok: true, result: outcome });
|
|
8536
8544
|
}
|
|
@@ -8802,6 +8810,19 @@ function readSkillsManifest(root) {
|
|
|
8802
8810
|
function few(names, limit = 6) {
|
|
8803
8811
|
return names.length <= limit ? names.join(", ") : `${names.slice(0, limit).join(", ")} and ${names.length - limit} more`;
|
|
8804
8812
|
}
|
|
8813
|
+
function isSkillFolder(folder) {
|
|
8814
|
+
return /^[a-z0-9][a-z0-9-]{0,63}$/.test(folder);
|
|
8815
|
+
}
|
|
8816
|
+
function retainedFolders(manifest, set2) {
|
|
8817
|
+
const retained = /* @__PURE__ */ new Map();
|
|
8818
|
+
for (const organization of set2.organizations) {
|
|
8819
|
+
const skipped = organization.skills.filter((skill) => !isSkillFolder(skill.folder));
|
|
8820
|
+
const held = Object.keys(manifest?.organizations[organization.id]?.skills ?? {});
|
|
8821
|
+
const kept = held.filter((folder) => skipped.some((skill) => folder.endsWith(`-${skill.name}`)));
|
|
8822
|
+
if (kept.length) retained.set(organization.id, kept);
|
|
8823
|
+
}
|
|
8824
|
+
return retained;
|
|
8825
|
+
}
|
|
8805
8826
|
function heldFolders(manifest) {
|
|
8806
8827
|
const held = /* @__PURE__ */ new Map();
|
|
8807
8828
|
for (const [folder, version] of Object.entries(manifest?.flat ?? {})) held.set(folder, version);
|
|
@@ -8811,9 +8832,10 @@ function heldFolders(manifest) {
|
|
|
8811
8832
|
}
|
|
8812
8833
|
function retirable(manifest, set2) {
|
|
8813
8834
|
const refreshed = new Set(set2.organizations.map((organization) => organization.id));
|
|
8835
|
+
const retained = new Set([...retainedFolders(manifest, set2).values()].flat());
|
|
8814
8836
|
const folders = [...Object.keys(manifest?.flat ?? {}), ...Object.keys(manifest?.cli ?? {})];
|
|
8815
8837
|
for (const [id, section] of Object.entries(manifest?.organizations ?? {})) {
|
|
8816
|
-
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills));
|
|
8838
|
+
if (set2.complete || refreshed.has(id)) folders.push(...Object.keys(section.skills).filter((folder) => !retained.has(folder)));
|
|
8817
8839
|
}
|
|
8818
8840
|
return folders;
|
|
8819
8841
|
}
|
|
@@ -8823,21 +8845,28 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8823
8845
|
const written = [];
|
|
8824
8846
|
const added = [];
|
|
8825
8847
|
const updated = [];
|
|
8848
|
+
const skipped = [];
|
|
8826
8849
|
const counts = [];
|
|
8827
8850
|
const write = (skill) => {
|
|
8828
|
-
if (
|
|
8851
|
+
if (!isSkillFolder(skill.folder)) {
|
|
8852
|
+
skipped.push(skill.folder);
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8829
8855
|
writeTextAtomic(path5.join(root, skill.folder, "SKILL.md"), skill.content, 420);
|
|
8830
8856
|
written.push(skill.folder);
|
|
8831
|
-
const
|
|
8832
|
-
if (
|
|
8833
|
-
else if (
|
|
8857
|
+
const before2 = held.get(skill.folder);
|
|
8858
|
+
if (before2 === void 0) added.push(skill.folder);
|
|
8859
|
+
else if (before2 !== skill.updatedAt) updated.push(skill.folder);
|
|
8834
8860
|
};
|
|
8835
8861
|
for (const organization of set2.organizations) {
|
|
8862
|
+
const before2 = written.length;
|
|
8836
8863
|
organization.skills.forEach(write);
|
|
8837
|
-
counts.push({ label: organization.name, count:
|
|
8864
|
+
counts.push({ label: organization.name, count: written.length - before2 });
|
|
8838
8865
|
}
|
|
8866
|
+
const before = written.length;
|
|
8839
8867
|
set2.cli.forEach(write);
|
|
8840
|
-
if (set2.cli.length) counts.push({ label: "CLI", count:
|
|
8868
|
+
if (set2.cli.length) counts.push({ label: "CLI", count: written.length - before });
|
|
8869
|
+
const retained = retainedFolders(previous, set2);
|
|
8841
8870
|
const removed = [];
|
|
8842
8871
|
const kept = new Set(written);
|
|
8843
8872
|
for (const folder of retirable(previous, set2)) {
|
|
@@ -8855,7 +8884,10 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8855
8884
|
organizations[organization.id] = {
|
|
8856
8885
|
name: organization.name,
|
|
8857
8886
|
installedAt: stamp,
|
|
8858
|
-
skills: Object.fromEntries(
|
|
8887
|
+
skills: Object.fromEntries([
|
|
8888
|
+
...(retained.get(organization.id) ?? []).map((folder) => [folder, previous.organizations[organization.id].skills[folder]]),
|
|
8889
|
+
...organization.skills.filter((skill) => isSkillFolder(skill.folder)).map((skill) => [skill.folder, skill.updatedAt])
|
|
8890
|
+
])
|
|
8859
8891
|
};
|
|
8860
8892
|
}
|
|
8861
8893
|
const manifest = {
|
|
@@ -8865,7 +8897,7 @@ function installSkills(root, set2, source, now = () => /* @__PURE__ */ new Date(
|
|
|
8865
8897
|
organizations
|
|
8866
8898
|
};
|
|
8867
8899
|
writeJsonAtomic(path5.join(root, SKILLS_MANIFEST), manifest, 420);
|
|
8868
|
-
return { root, written, added, updated, removed, counts };
|
|
8900
|
+
return { root, written, added, updated, removed, skipped, counts };
|
|
8869
8901
|
}
|
|
8870
8902
|
function skillsStatus(root, set2) {
|
|
8871
8903
|
const manifest = readSkillsManifest(root);
|
|
@@ -8873,7 +8905,9 @@ function skillsStatus(root, set2) {
|
|
|
8873
8905
|
const current = [];
|
|
8874
8906
|
const behind = [];
|
|
8875
8907
|
const missing = [];
|
|
8876
|
-
const
|
|
8908
|
+
const all = [...set2.organizations.flatMap((organization) => organization.skills), ...set2.cli];
|
|
8909
|
+
const served = all.filter((skill) => isSkillFolder(skill.folder));
|
|
8910
|
+
const skipped = all.filter((skill) => !isSkillFolder(skill.folder)).map((skill) => skill.folder);
|
|
8877
8911
|
for (const skill of served) {
|
|
8878
8912
|
const before = held.get(skill.folder);
|
|
8879
8913
|
if (before === void 0 || !fs6.existsSync(path5.join(root, skill.folder, "SKILL.md"))) missing.push(skill.folder);
|
|
@@ -8882,7 +8916,7 @@ function skillsStatus(root, set2) {
|
|
|
8882
8916
|
}
|
|
8883
8917
|
const servedFolders = new Set(served.map((skill) => skill.folder));
|
|
8884
8918
|
const retired = [...new Set(retirable(manifest, set2))].filter((folder) => !servedFolders.has(folder));
|
|
8885
|
-
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired };
|
|
8919
|
+
return { root, installedAt: manifest?.installedAt || null, current, behind, missing, retired, skipped };
|
|
8886
8920
|
}
|
|
8887
8921
|
|
|
8888
8922
|
// src/skills/installable.ts
|
|
@@ -8940,8 +8974,11 @@ var skillsUsage = `diverge skills <list|install|refresh|status> [--organization-
|
|
|
8940
8974
|
|
|
8941
8975
|
A skill is a procedure over the tools it names; you get the ones whose every
|
|
8942
8976
|
tool you may run, in every organization the login can act in. Each lands as
|
|
8943
|
-
<organization prefix>-<name> \u2014 the organization's
|
|
8944
|
-
its name
|
|
8977
|
+
<organization prefix>-<name> \u2014 the organization's handle, or a slug of
|
|
8978
|
+
its name; the platform shortens a longer one to the 64 characters a harness
|
|
8979
|
+
accepts \u2014 and its SKILL.md opens with the organization its tools run in.
|
|
8980
|
+
A folder that is not a skill name is skipped with a warning naming it, and
|
|
8981
|
+
the folder that skill was installed under before stays.
|
|
8945
8982
|
--organization-id refreshes one organization and leaves the others as they
|
|
8946
8983
|
are; without it the whole login is refreshed, and an organization the login
|
|
8947
8984
|
no longer lists is retired. Without --harness, the harnesses present on this
|
|
@@ -8980,6 +9017,11 @@ function harnessesOf(flag, env) {
|
|
|
8980
9017
|
function installSet(harness, skills) {
|
|
8981
9018
|
return { cli: bundledSkills(harness), organizations: skills.organizations, complete: skills.complete };
|
|
8982
9019
|
}
|
|
9020
|
+
var SKIPPED = "not installed, the folder is not a skill name (at most 64 lowercase letters, digits and hyphens)";
|
|
9021
|
+
function skippedNote(results) {
|
|
9022
|
+
const skipped = [...new Set(results.flatMap((result) => result.skipped))];
|
|
9023
|
+
return skipped.length ? [`${skipped.length} ${SKIPPED}: ${few(skipped)}`] : [];
|
|
9024
|
+
}
|
|
8983
9025
|
function installReport(harness, result) {
|
|
8984
9026
|
const holdings = result.counts.map((entry) => `${entry.label} ${entry.count}`).join(", ");
|
|
8985
9027
|
const changes = [
|
|
@@ -9007,11 +9049,13 @@ async function refreshSkills(io, apiUrl, options = {}) {
|
|
|
9007
9049
|
io.stderr(`warning: skills not refreshed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9008
9050
|
return;
|
|
9009
9051
|
}
|
|
9052
|
+
const results = [];
|
|
9010
9053
|
for (const harness of harnesses) {
|
|
9011
9054
|
const result = installSkills(skillsRoot(harness, io.env), installSet(harness, skills), apiUrl);
|
|
9012
9055
|
if (!options.quiet || result.added.length || result.updated.length || result.removed.length) io.stdout(installReport(harness, result));
|
|
9056
|
+
results.push(result);
|
|
9013
9057
|
}
|
|
9014
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9058
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9015
9059
|
}
|
|
9016
9060
|
async function runSkills(args, io) {
|
|
9017
9061
|
const [verb, ...rest] = args;
|
|
@@ -9033,8 +9077,8 @@ async function runSkills(args, io) {
|
|
|
9033
9077
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId);
|
|
9034
9078
|
const harnesses = values.into ? [harnessesOf(values.harness ?? HARNESSES[0], io.env)[0]] : harnessesOf(values.harness, io.env);
|
|
9035
9079
|
if (harnesses.length === 0) throw new CliError("No coding-agent harness on this machine yet (Claude Code, Codex or dsh); name one with --harness to write its folder anyway.");
|
|
9036
|
-
|
|
9037
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9080
|
+
const results = harnesses.map((harness) => installForHarness(io, harness, skills, apiUrl, values.into));
|
|
9081
|
+
for (const note of [...skills.notes, ...skippedNote(results)]) io.stderr(`warning: ${note}`);
|
|
9038
9082
|
return ExitCode.Ok;
|
|
9039
9083
|
}
|
|
9040
9084
|
case "refresh": {
|
|
@@ -9050,6 +9094,7 @@ async function runSkills(args, io) {
|
|
|
9050
9094
|
if (status2.behind.length) parts.push(`${status2.behind.length} behind (${few(status2.behind)})`);
|
|
9051
9095
|
if (status2.missing.length) parts.push(`${status2.missing.length} missing${status2.current.length || status2.behind.length ? ` (${few(status2.missing)})` : ""}`);
|
|
9052
9096
|
if (status2.retired.length) parts.push(`${status2.retired.length} retired (${few(status2.retired)})`);
|
|
9097
|
+
if (status2.skipped.length) parts.push(`${status2.skipped.length} ${SKIPPED} (${few(status2.skipped)})`);
|
|
9053
9098
|
behind ||= status2.behind.length + status2.missing.length + status2.retired.length > 0;
|
|
9054
9099
|
io.stdout(`${harness}: ${parts.join(", ")} in ${status2.root}${status2.installedAt ? ` (installed ${status2.installedAt})` : " (never installed)"}`);
|
|
9055
9100
|
}
|
|
@@ -9584,6 +9629,11 @@ var MAX_PENDING_EVENTS = 2e4;
|
|
|
9584
9629
|
var DEFAULT_POLL_MS = 2e3;
|
|
9585
9630
|
var LAUNCH_SLACK_MS = 5e3;
|
|
9586
9631
|
var STALLED_TICKS = 30;
|
|
9632
|
+
var CAPTURE_NOTICES = {
|
|
9633
|
+
off: "This organization does not record agent sessions (Forge installation config agentSessions.capture is off); nothing is sent.",
|
|
9634
|
+
metadata: "Recorded: metadata only \u2014 which tools ran and how many failed, tokens, models and timing; no prompt, reply, tool argument or result.",
|
|
9635
|
+
conversation: "Recorded: the whole conversation \u2014 every prompt, reply, tool call and its result. Yours to read; anyone else in the organization needs the Agent Conversations role, and every such read is recorded on the session."
|
|
9636
|
+
};
|
|
9587
9637
|
function eventBytes(event) {
|
|
9588
9638
|
return Buffer.byteLength(JSON.stringify(event), "utf8");
|
|
9589
9639
|
}
|
|
@@ -9612,6 +9662,8 @@ var DshSessionPublisher = class {
|
|
|
9612
9662
|
loop = null;
|
|
9613
9663
|
/** The organization does not record: nothing more is read or sent. */
|
|
9614
9664
|
disabled = false;
|
|
9665
|
+
/** The level the person was last told. */
|
|
9666
|
+
announced = null;
|
|
9615
9667
|
ticking = null;
|
|
9616
9668
|
/** The directory dsh writes this launch's sessions under. */
|
|
9617
9669
|
get projectDir() {
|
|
@@ -9781,9 +9833,12 @@ var DshSessionPublisher = class {
|
|
|
9781
9833
|
return;
|
|
9782
9834
|
}
|
|
9783
9835
|
session.reportedFailure = null;
|
|
9784
|
-
if (
|
|
9836
|
+
if (result.capture !== this.announced) {
|
|
9837
|
+
this.announced = result.capture;
|
|
9838
|
+
this.deps.report(CAPTURE_NOTICES[result.capture]);
|
|
9839
|
+
}
|
|
9840
|
+
if (result.capture === "off") {
|
|
9785
9841
|
this.disabled = true;
|
|
9786
|
-
this.deps.report("This organization does not record agent sessions (Forge installation config agentSessions.recording is off); nothing is sent.");
|
|
9787
9842
|
return;
|
|
9788
9843
|
}
|
|
9789
9844
|
session.opened = true;
|
|
@@ -9901,8 +9956,8 @@ function syncDivergentPreset(io, binary, home) {
|
|
|
9901
9956
|
async function skillsFor(io, apiUrl, harness, organizationId) {
|
|
9902
9957
|
try {
|
|
9903
9958
|
const skills = await loginSkillSet(apiUrl, io.env, organizationId?.trim() || void 0);
|
|
9904
|
-
installForHarness(io, harness, skills, apiUrl);
|
|
9905
|
-
for (const note of skills.notes) io.stderr(`warning: ${note}`);
|
|
9959
|
+
const result = installForHarness(io, harness, skills, apiUrl);
|
|
9960
|
+
for (const note of [...skills.notes, ...skippedNote([result])]) io.stderr(`warning: ${note}`);
|
|
9906
9961
|
} catch (error) {
|
|
9907
9962
|
io.stderr(`warning: skills not installed (${error instanceof Error ? error.message : String(error)}); run: diverge skills install`);
|
|
9908
9963
|
}
|
|
@@ -10033,7 +10088,7 @@ async function run(args, io) {
|
|
|
10033
10088
|
const source = tokenSource(io.env, apiUrl);
|
|
10034
10089
|
const organizationId = tokenClaims(token.access_token).organizationId ?? values["organization-id"];
|
|
10035
10090
|
if (!organizationId) throw new CliError("the gateway token names no organization; pass --organization-id");
|
|
10036
|
-
io.stderr(`This session is
|
|
10091
|
+
io.stderr(`This session is organization ${organizationId}'s own record at the level its administrators set \u2014 off, metadata (tools, tokens, models; no content) or the whole conversation \u2014 named once its record opens. Yours to read: diverge tools call ${AGENT_SESSION_LIST_TOOL} (ADR 0121).`);
|
|
10037
10092
|
const tools = new ToolsClient({ apiUrl, organizationId, mode: "operator", clientId: clientId(io.env) }, io.env);
|
|
10038
10093
|
const publisher = new DshSessionPublisher({
|
|
10039
10094
|
sessionsRoot: path14.join(home, "sessions"),
|
|
@@ -10076,8 +10131,10 @@ async function appendSession(tools, input) {
|
|
|
10076
10131
|
if (response.success === false || response.error) throw new CliError(response.error ?? "the platform refused the append");
|
|
10077
10132
|
if (isPending(response.result)) throw new CliError("the append was raised for a decision, which an inert record never needs");
|
|
10078
10133
|
const result = response.result;
|
|
10079
|
-
if (!result ||
|
|
10080
|
-
|
|
10134
|
+
if (!result || !(result.capture === "off" || result.capture === "metadata" || result.capture === "conversation")) {
|
|
10135
|
+
throw new CliError("the platform answered the append without the level it records");
|
|
10136
|
+
}
|
|
10137
|
+
return { capture: result.capture, sessionId: result.sessionId ?? null, stored: result.stored ?? 0, skipped: result.skipped ?? 0, lastSeq: typeof result.lastSeq === "number" ? result.lastSeq : -1 };
|
|
10081
10138
|
}
|
|
10082
10139
|
async function status(args, io) {
|
|
10083
10140
|
const { values } = parseArgs3({ args, options: { "organization-id": { type: "string" } }, strict: true });
|
|
@@ -10891,13 +10948,19 @@ import { parseArgs as parseArgs7 } from "node:util";
|
|
|
10891
10948
|
var pluginsUsage = `diverge plugins <validate|publish|list|retire> [--organization-id <id>] ...
|
|
10892
10949
|
|
|
10893
10950
|
validate <manifest.json> (dry run: the platform's full validation, nothing stored)
|
|
10894
|
-
publish <manifest.json> [--json] (forge_plugins:create in the signed-in organization
|
|
10951
|
+
publish <manifest.json> [--json] (forge_plugins:create in the signed-in organization;
|
|
10952
|
+
a version that changes authority waits on a decision)
|
|
10895
10953
|
list [--json]
|
|
10896
10954
|
retire <pluginId>
|
|
10897
10955
|
|
|
10898
10956
|
Register a tenant plugin: a plugin whose code runs in your organization's own
|
|
10899
10957
|
runtime and never inside the platform. The manifest lists the tools the
|
|
10900
|
-
platform registers, pinned; a change is a new version published again.
|
|
10958
|
+
platform registers, pinned; a change is a new version published again.
|
|
10959
|
+
|
|
10960
|
+
A version that only reshapes or removes the tools already served is stored at
|
|
10961
|
+
once. Anything else changes authority: publish raises it as
|
|
10962
|
+
forge_publish_plugin, prints the invocation, and exits 3; a person decides it
|
|
10963
|
+
in Compass, and diverge tools invocation <id> reads the outcome. Enable
|
|
10901
10964
|
it with forge plugins (or forge_set_plugin_state) and bind the runtime with a
|
|
10902
10965
|
Forge bridge binding whose runtimeType matches the manifest's runtime.type.`;
|
|
10903
10966
|
var SCOPE_OPTIONS2 = { "organization-id": { type: "string" } };
|
|
@@ -10905,7 +10968,28 @@ async function client2(values, io) {
|
|
|
10905
10968
|
const settings = loadToolsSettings({ organizationId: values["organization-id"] }, io.env);
|
|
10906
10969
|
io.stderr(scopeLine(settings));
|
|
10907
10970
|
io.stderr(await actingAsLine(settings, io.env));
|
|
10908
|
-
return new ToolsClient(settings, io.env);
|
|
10971
|
+
return { settings, tools: new ToolsClient(settings, io.env) };
|
|
10972
|
+
}
|
|
10973
|
+
function printProblems(details, io) {
|
|
10974
|
+
io.stderr(`Invalid manifest (${details.length} problem${details.length === 1 ? "" : "s"}):`);
|
|
10975
|
+
for (const detail of details) io.stderr(` - ${detail}`);
|
|
10976
|
+
return ExitCode.Failure;
|
|
10977
|
+
}
|
|
10978
|
+
async function raisePublish(manifest, authorityChanges, context, io) {
|
|
10979
|
+
const { settings, tools, json } = context;
|
|
10980
|
+
if (!json) {
|
|
10981
|
+
io.stdout("This version changes authority, so it is stored only once it is decided:");
|
|
10982
|
+
for (const change of authorityChanges) io.stdout(` - ${change}`);
|
|
10983
|
+
}
|
|
10984
|
+
const outcome = await runOne({ tool: "forge_publish_plugin", args: { manifest } }, {
|
|
10985
|
+
callTool: (name, args) => tools.callTool(name, args),
|
|
10986
|
+
decideAt: (pending) => `${settings.apiUrl}${pending.approvalUrl ?? ""}`,
|
|
10987
|
+
...json ? {} : { onPending: (pending) => formatPending(pending, settings.apiUrl, settings.organizationId).forEach((line) => io.stdout(line)) }
|
|
10988
|
+
});
|
|
10989
|
+
if (json) io.stdout(JSON.stringify({ authorityChanges, ...outcome }, null, 2));
|
|
10990
|
+
else if (outcome.status === "executed") io.stdout("Published.");
|
|
10991
|
+
else if (outcome.status === "failed") throw new CliError(`forge_publish_plugin failed: ${outcome.error}`);
|
|
10992
|
+
return worstExitCode([outcome]);
|
|
10909
10993
|
}
|
|
10910
10994
|
function readManifest(file) {
|
|
10911
10995
|
if (!file) throw new CliError(`A manifest file is required.
|
|
@@ -10942,30 +11026,30 @@ async function runPlugins(args, io) {
|
|
|
10942
11026
|
case "validate": {
|
|
10943
11027
|
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
10944
11028
|
const manifest = readManifest(positionals[0]);
|
|
10945
|
-
const tools = await client2(values, io);
|
|
11029
|
+
const { tools } = await client2(values, io);
|
|
10946
11030
|
const outcome = await tools.publishPlugin(manifest, { dryRun: true });
|
|
10947
|
-
if (!outcome.ok)
|
|
10948
|
-
io.stderr(`Invalid manifest (${outcome.details.length} problem${outcome.details.length === 1 ? "" : "s"}):`);
|
|
10949
|
-
for (const detail of outcome.details) io.stderr(` - ${detail}`);
|
|
10950
|
-
return ExitCode.Failure;
|
|
10951
|
-
}
|
|
11031
|
+
if (!outcome.ok) return printProblems(outcome.details, io);
|
|
10952
11032
|
if (values.json) io.stdout(JSON.stringify(outcome.result, null, 2));
|
|
10953
11033
|
else {
|
|
10954
11034
|
io.stdout(`Valid. ${outcome.result.changed ? "Publishing would register" : "Already published at this digest:"}`);
|
|
10955
11035
|
for (const line of formatPlugin(outcome.result.plugin)) io.stdout(line);
|
|
11036
|
+
const authority = outcome.result.authorityChanges ?? [];
|
|
11037
|
+
if (authority.length) {
|
|
11038
|
+
io.stdout("Publishing it changes authority, so it waits on a person's decision in Compass:");
|
|
11039
|
+
for (const change of authority) io.stdout(` - ${change}`);
|
|
11040
|
+
}
|
|
10956
11041
|
}
|
|
10957
11042
|
return ExitCode.Ok;
|
|
10958
11043
|
}
|
|
10959
11044
|
case "publish": {
|
|
10960
11045
|
const { values, positionals } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, allowPositionals: true, strict: true });
|
|
10961
11046
|
const manifest = readManifest(positionals[0]);
|
|
10962
|
-
const tools = await client2(values, io);
|
|
11047
|
+
const { settings, tools } = await client2(values, io);
|
|
10963
11048
|
const outcome = await tools.publishPlugin(manifest, { dryRun: false });
|
|
10964
|
-
if (!outcome.ok) {
|
|
10965
|
-
|
|
10966
|
-
for (const detail of outcome.details) io.stderr(` - ${detail}`);
|
|
10967
|
-
return ExitCode.Failure;
|
|
11049
|
+
if (!outcome.ok && outcome.authorityChange) {
|
|
11050
|
+
return raisePublish(manifest, outcome.details, { settings, tools, json: values.json }, io);
|
|
10968
11051
|
}
|
|
11052
|
+
if (!outcome.ok) return printProblems(outcome.details, io);
|
|
10969
11053
|
if (values.json) io.stdout(JSON.stringify(outcome.result, null, 2));
|
|
10970
11054
|
else {
|
|
10971
11055
|
io.stdout(outcome.result.changed ? "Published." : "Unchanged: this digest was already published.");
|
|
@@ -10976,7 +11060,7 @@ async function runPlugins(args, io) {
|
|
|
10976
11060
|
}
|
|
10977
11061
|
case "list": {
|
|
10978
11062
|
const { values } = parseArgs7({ args: rest, options: { ...SCOPE_OPTIONS2, json: { type: "boolean", default: false } }, strict: true });
|
|
10979
|
-
const tools = await client2(values, io);
|
|
11063
|
+
const { tools } = await client2(values, io);
|
|
10980
11064
|
const plugins = await tools.listPlugins();
|
|
10981
11065
|
if (values.json) io.stdout(JSON.stringify(plugins, null, 2));
|
|
10982
11066
|
else if (!plugins.length) io.stdout("No tenant plugins published for this organization.");
|
|
@@ -10989,7 +11073,7 @@ async function runPlugins(args, io) {
|
|
|
10989
11073
|
if (!pluginId) throw new CliError(`A plugin id is required.
|
|
10990
11074
|
|
|
10991
11075
|
${pluginsUsage}`);
|
|
10992
|
-
const tools = await client2(values, io);
|
|
11076
|
+
const { tools } = await client2(values, io);
|
|
10993
11077
|
const plugin = await tools.retirePlugin(pluginId);
|
|
10994
11078
|
io.stdout(`Retired ${plugin.pluginId}@${plugin.version}; its ${plugin.toolNames.length} tool(s) are gone. Installation records stay for a re-publish.`);
|
|
10995
11079
|
return ExitCode.Ok;
|
package/package.json
CHANGED