c8ctl-plugin-nano 1.44.1 → 1.44.3
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/agentic.mjs +9 -0
- package/c8ctl-plugin.js +86 -85
- package/package.json +10 -10
package/agentic.mjs
CHANGED
|
@@ -74,6 +74,15 @@ export * as presence from '@nanobpm/agentic/presence';
|
|
|
74
74
|
export * as relay from '@nanobpm/agentic/relay';
|
|
75
75
|
export * as transcript from '@nanobpm/agentic/transcript';
|
|
76
76
|
|
|
77
|
+
// ACP → transcript bridge (nanobpm/nano-ide#534). The canonical, pure wire seam
|
|
78
|
+
// that turns one raw ACP `session/update` into the exact `{ nwfTranscriptEvent:
|
|
79
|
+
// 1, kind, … }` transcript-chunk bytes the cockpit decodes — `classifyUpdate`
|
|
80
|
+
// composed with `encodeTranscriptEvent` behind the single `acpUpdateToTranscriptChunk`
|
|
81
|
+
// helper. The ACP executor (`spawnCaptureAcp`) consumes it through THIS surface
|
|
82
|
+
// instead of hand-rolling an envelope grammar, so there is one producer of the
|
|
83
|
+
// wire and the shared conformance corpus pins it to the hub.
|
|
84
|
+
export * as sessionAcp from '@nanobpm/agentic/session/acp';
|
|
85
|
+
|
|
77
86
|
// ---------------------------------------------------------------------------
|
|
78
87
|
// Demand read — @nanobpm/agentic/demand.
|
|
79
88
|
//
|
package/c8ctl-plugin.js
CHANGED
|
@@ -63,6 +63,11 @@ import { platformForHost } from './platforms.mjs';
|
|
|
63
63
|
import { createWorkChannel, redactAgenticUrl, buildAgenticUrl } from './work-channel.mjs';
|
|
64
64
|
import { createRelaySession, roleTerminalMode } from './work-relay.mjs';
|
|
65
65
|
import { createBufferMonitor, resolveBufferCapacity } from './work-buffer.mjs';
|
|
66
|
+
// Canonical ACP → transcript wire bridge (nanobpm/nano-ide#534), consumed through
|
|
67
|
+
// the single agentic import surface. `acpUpdateToTranscriptChunk(update)` maps one
|
|
68
|
+
// raw ACP `session/update` to the exact transcript-chunk bytes the cockpit decodes,
|
|
69
|
+
// replacing the plugin's former hand-rolled `nwfTranscriptEvent` envelope grammar.
|
|
70
|
+
import { sessionAcp as agenticSessionAcp } from './agentic.mjs';
|
|
66
71
|
|
|
67
72
|
const requireFromHere = createRequire(import.meta.url);
|
|
68
73
|
const pluginDir = dirname(fileURLToPath(import.meta.url));
|
|
@@ -188,6 +193,28 @@ function getLogger() {
|
|
|
188
193
|
};
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
/**
|
|
197
|
+
* True when c8ctl's effective output mode is JSON.
|
|
198
|
+
*
|
|
199
|
+
* The plugin used to declare its own `--json` flag, but it collided with
|
|
200
|
+
* c8ctl's new global `--json` and was dropped (see #122). As a result the
|
|
201
|
+
* global `--json` (and persisted `output json` / `C8CTL_OUTPUT_MODE`) is no
|
|
202
|
+
* longer delivered to the plugin as `flags.json`. Read the host's effective
|
|
203
|
+
* output mode directly — the same signal c8ctl's own bundled plugins use — so
|
|
204
|
+
* `workforce list/status` still emit machine-readable JSON on stdout.
|
|
205
|
+
*/
|
|
206
|
+
function hostOutputIsJson() {
|
|
207
|
+
const c = globalThis.c8ctl;
|
|
208
|
+
if (!c) return false;
|
|
209
|
+
try {
|
|
210
|
+
if (c.outputMode === 'json') return true;
|
|
211
|
+
const l = typeof c.getLogger === 'function' ? c.getLogger() : null;
|
|
212
|
+
return !!l && l.mode === 'json';
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
191
218
|
/** Expand a leading `~` to the user's home directory. */
|
|
192
219
|
function expandHome(p) {
|
|
193
220
|
if (!p) return p;
|
|
@@ -4143,15 +4170,17 @@ const ACP_MAX_LINE_BYTES = 8 * 1024 * 1024; // 8 MiB
|
|
|
4143
4170
|
|
|
4144
4171
|
// Drive an ACP (Agent Client Protocol) agent over JSON-RPC 2.0 on stdio.
|
|
4145
4172
|
//
|
|
4146
|
-
// This executor drives ACP end-to-end. Each `session/update` is mapped to
|
|
4147
|
-
//
|
|
4148
|
-
//
|
|
4149
|
-
//
|
|
4150
|
-
// `
|
|
4151
|
-
//
|
|
4152
|
-
//
|
|
4153
|
-
//
|
|
4154
|
-
//
|
|
4173
|
+
// This executor drives ACP end-to-end. Each `session/update` is mapped to the
|
|
4174
|
+
// CANONICAL transcript-chunk wire form via the shared `@nanobpm/agentic` bridge
|
|
4175
|
+
// (`acpUpdateToTranscriptChunk` — nanobpm/nano-ide#534) and published on the relay
|
|
4176
|
+
// session's transcript-chunk seam (`relayTap.relayTranscriptChunk`) — the exact
|
|
4177
|
+
// `{ nwfTranscriptEvent: 1, kind, … }` bytes the cockpit's `parseTranscriptEvent` +
|
|
4178
|
+
// `deriveView` decode into messages / tool cards. The raw relay TRANSPORT is
|
|
4179
|
+
// untouched (still `relaySession.relay(text)`); only the payload shape on the lane
|
|
4180
|
+
// changes. When an update has no canonical mapping (an `ignored` classification), or
|
|
4181
|
+
// the relay exposes no transcript seam (minimal mode / a plain tap), it falls back
|
|
4182
|
+
// to the human-TEXT chunk on the same lane (`relayTap.onData`) so nothing is
|
|
4183
|
+
// dropped. The raw JSON-RPC is never tee'd either way.
|
|
4155
4184
|
//
|
|
4156
4185
|
// Framing: ACP frames are newline-delimited JSON-RPC 2.0 messages on stdio (one
|
|
4157
4186
|
// compact JSON object per line, `\n`-terminated). We implement a tiny inline
|
|
@@ -4234,9 +4263,9 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4234
4263
|
|
|
4235
4264
|
// Local mirrors of a human text chunk: the --stream spy tee and the byte-
|
|
4236
4265
|
// capped stdout capture (what the result envelope carries). Deliberately does
|
|
4237
|
-
// NOT touch the relay lane, so a
|
|
4238
|
-
// text locally (for the result + spy) WITHOUT also re-emitting raw text
|
|
4239
|
-
// the relay lane — which
|
|
4266
|
+
// NOT touch the relay lane, so a canonical-transcript update can mirror its
|
|
4267
|
+
// human text locally (for the result + spy) WITHOUT also re-emitting raw text
|
|
4268
|
+
// onto the relay lane — which carries the canonical transcript chunk instead.
|
|
4240
4269
|
const captureHuman = (text) => {
|
|
4241
4270
|
if (!text) return;
|
|
4242
4271
|
const buf = Buffer.from(text, 'utf8');
|
|
@@ -4251,8 +4280,8 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4251
4280
|
// use: the relay tap (framed + jobKey-tagged by the caller) and the local
|
|
4252
4281
|
// --stream spy tee. Byte-capped like the raw captures. This is the minimal-
|
|
4253
4282
|
// mode text path — used for stderr, and as the fallback for any session/update
|
|
4254
|
-
// that has no
|
|
4255
|
-
//
|
|
4283
|
+
// that has no canonical transcript mapping (or when the relay exposes no
|
|
4284
|
+
// transcript-chunk seam).
|
|
4256
4285
|
const emitHuman = (text) => {
|
|
4257
4286
|
if (!text) return;
|
|
4258
4287
|
if (relayTap && typeof relayTap.onData === 'function') {
|
|
@@ -4373,75 +4402,45 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4373
4402
|
}
|
|
4374
4403
|
};
|
|
4375
4404
|
|
|
4376
|
-
// #110
|
|
4377
|
-
//
|
|
4378
|
-
//
|
|
4379
|
-
//
|
|
4380
|
-
//
|
|
4381
|
-
//
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
// consumers see omitted/optional strings, not coerced nulls. `status`
|
|
4392
|
-
// falls through to the kind's default only when genuinely absent.
|
|
4393
|
-
const toolOf = (u, defaultStatus) => ({
|
|
4394
|
-
id: u.toolCallId ?? undefined,
|
|
4395
|
-
title: u.title ?? undefined,
|
|
4396
|
-
status: u.status ?? defaultStatus ?? undefined,
|
|
4397
|
-
kind: u.kind ?? undefined,
|
|
4398
|
-
});
|
|
4399
|
-
switch (kind) {
|
|
4400
|
-
case 'agent_message_chunk':
|
|
4401
|
-
return { ...base, kind: 'message', role: 'agent', text: acpTextOf(update.content) };
|
|
4402
|
-
case 'agent_thought_chunk':
|
|
4403
|
-
return { ...base, kind: 'thought', role: 'agent', text: acpTextOf(update.content) };
|
|
4404
|
-
case 'user_message_chunk':
|
|
4405
|
-
return { ...base, kind: 'message', role: 'user', text: acpTextOf(update.content) };
|
|
4406
|
-
case 'tool_call':
|
|
4407
|
-
// `text` mirrors the fallback's plain text so the typed envelope stays
|
|
4408
|
-
// self-contained for lightweight renderers (matches the stated contract).
|
|
4409
|
-
return { ...base, kind: 'tool_call', text: describeUpdate(update), tool: toolOf(update, 'pending') };
|
|
4410
|
-
case 'tool_call_update':
|
|
4411
|
-
return { ...base, kind: 'tool_call_update', text: describeUpdate(update), tool: toolOf(update, undefined) };
|
|
4412
|
-
case 'plan':
|
|
4413
|
-
// Carry the actual plan entries (rich cockpit renders them), not a
|
|
4414
|
-
// count — an absent/malformed payload stays `undefined` (omitted).
|
|
4415
|
-
return { ...base, kind: 'plan', entries: Array.isArray(update.entries) ? update.entries : undefined };
|
|
4416
|
-
default:
|
|
4417
|
-
// Unmodelled kind → no typed envelope; caller uses the text fallback.
|
|
4418
|
-
return null;
|
|
4419
|
-
}
|
|
4405
|
+
// #110 / nanobpm/nano-ide#534: map an ACP session/update to the CANONICAL
|
|
4406
|
+
// transcript-chunk wire form via the shared `@nanobpm/agentic` bridge —
|
|
4407
|
+
// `classifyUpdate` composed with `encodeTranscriptEvent` behind the single
|
|
4408
|
+
// `acpUpdateToTranscriptChunk` helper. It returns the exact
|
|
4409
|
+
// `{ nwfTranscriptEvent: 1, kind, … }` bytes the cockpit's `parseTranscriptEvent`
|
|
4410
|
+
// decodes and `deriveView` folds into messages / tool cards, or `null` for an
|
|
4411
|
+
// update with no canonical meaning (an `ignored` classification: a plan, an
|
|
4412
|
+
// intermediate tool_call_update, a non-text chunk, or a malformed update). No
|
|
4413
|
+
// envelope grammar or vocab is hand-rolled here anymore — the marker, version,
|
|
4414
|
+
// kinds and fields all come from the package, so a producer and a consumer can
|
|
4415
|
+
// never diverge on the wire again. `null` (and any bridge throw) falls through
|
|
4416
|
+
// to the minimal human-text path below, so nothing is ever dropped.
|
|
4417
|
+
const encodeTranscriptChunk = (update) => {
|
|
4418
|
+
try { return agenticSessionAcp.acpUpdateToTranscriptChunk(update); }
|
|
4419
|
+
catch { return null; }
|
|
4420
4420
|
};
|
|
4421
4421
|
|
|
4422
|
-
// Publish a session/update: prefer the
|
|
4423
|
-
//
|
|
4424
|
-
//
|
|
4425
|
-
// nothing is ever dropped (no regression vs minimal mode).
|
|
4422
|
+
// Publish a session/update: prefer the canonical transcript chunk on the
|
|
4423
|
+
// relay's transcript-chunk seam; fall back to the minimal human-text path when
|
|
4424
|
+
// the update has no canonical mapping OR the relay exposes no transcript seam,
|
|
4425
|
+
// so nothing is ever dropped (no regression vs minimal mode).
|
|
4426
4426
|
const emitTranscript = (update) => {
|
|
4427
|
-
const
|
|
4428
|
-
if (
|
|
4429
|
-
// Only skip the text fallback when the
|
|
4427
|
+
const chunk = encodeTranscriptChunk(update);
|
|
4428
|
+
if (chunk && relayTap && typeof relayTap.relayTranscriptChunk === 'function') {
|
|
4429
|
+
// Only skip the text fallback when the chunk publish ACTUALLY succeeded.
|
|
4430
4430
|
// If the seam throws (a downstream tap implementation, not just the
|
|
4431
|
-
// built-in best-effort
|
|
4432
|
-
//
|
|
4433
|
-
//
|
|
4434
|
-
// guarantee.
|
|
4431
|
+
// built-in best-effort guard), the chunk never reached the relay lane — so
|
|
4432
|
+
// we must fall through to the text path or that update would be silently
|
|
4433
|
+
// dropped, breaking the "nothing is ever dropped" guarantee.
|
|
4435
4434
|
let published = false;
|
|
4436
|
-
try { relayTap.
|
|
4435
|
+
try { relayTap.relayTranscriptChunk(chunk); published = true; } catch { /* relay best-effort */ }
|
|
4437
4436
|
if (published) {
|
|
4438
4437
|
// Mirror the human text locally (spy tee + captured stdout) so the
|
|
4439
4438
|
// result envelope and --stream spy are unchanged — without re-emitting
|
|
4440
|
-
// raw text onto the relay lane, which now carries the
|
|
4439
|
+
// raw text onto the relay lane, which now carries the canonical chunk.
|
|
4441
4440
|
captureHuman(describeUpdate(update));
|
|
4442
4441
|
return;
|
|
4443
4442
|
}
|
|
4444
|
-
//
|
|
4443
|
+
// Chunk publish threw → fall through to the text lane below.
|
|
4445
4444
|
}
|
|
4446
4445
|
// Fallback: minimal text-chunk path (relay text + spy tee + capture).
|
|
4447
4446
|
emitHuman(describeUpdate(update));
|
|
@@ -4803,17 +4802,18 @@ function runAgentJob(profile, job, opts = {}) {
|
|
|
4803
4802
|
const relayTap = relaySession
|
|
4804
4803
|
? {
|
|
4805
4804
|
onData: (buf) => relaySession.relay(buf),
|
|
4806
|
-
// #110
|
|
4807
|
-
// session/update to
|
|
4808
|
-
//
|
|
4809
|
-
//
|
|
4810
|
-
//
|
|
4811
|
-
//
|
|
4812
|
-
//
|
|
4813
|
-
//
|
|
4814
|
-
//
|
|
4815
|
-
|
|
4816
|
-
|
|
4805
|
+
// #110 / nanobpm/nano-ide#534: canonical transcript-chunk publish seam. The
|
|
4806
|
+
// ACP producer maps each session/update to the exact `{ nwfTranscriptEvent:
|
|
4807
|
+
// 1, kind, … }` chunk bytes via `acpUpdateToTranscriptChunk` and hands them
|
|
4808
|
+
// here PRE-ENCODED; we relay them verbatim (newline-framed) onto the SAME
|
|
4809
|
+
// relay lane — the raw relay TRANSPORT (ring/QoS/offsets/jobKey routing) is
|
|
4810
|
+
// unchanged, still `relaySession.relay(text)`. Consumers (cockpit
|
|
4811
|
+
// parseTranscriptEvent + deriveView) decode the chunk; unmapped updates fall
|
|
4812
|
+
// back to the `onData` text path so nothing is dropped (no regression vs the
|
|
4813
|
+
// minimal-mode floor). Best-effort: a relay-transport failure must never
|
|
4814
|
+
// crash the worker, so swallow here.
|
|
4815
|
+
relayTranscriptChunk: (chunk) => {
|
|
4816
|
+
try { relaySession.relay(`${chunk}\n`); } catch { /* relay best-effort */ }
|
|
4817
4817
|
},
|
|
4818
4818
|
attachSteer: (write) => relaySession.attachSteer(write),
|
|
4819
4819
|
}
|
|
@@ -8893,7 +8893,7 @@ async function workforceRemoveCmd(req, flags, manifestName) {
|
|
|
8893
8893
|
|
|
8894
8894
|
async function workforceListCmd(req, flags, manifestName) {
|
|
8895
8895
|
const logger = getLogger();
|
|
8896
|
-
const json = coerceBool(flags?.json, false);
|
|
8896
|
+
const json = flags?.json === false ? false : (coerceBool(flags?.json, false) || hostOutputIsJson());
|
|
8897
8897
|
const explicitManifest = lastManifestValue(flags) !== '';
|
|
8898
8898
|
const manifest = readWorkforceManifestStrict(manifestName);
|
|
8899
8899
|
const others = explicitManifest ? null : listWorkforceManifestNames();
|
|
@@ -9007,7 +9007,7 @@ async function workforceStartCmd(req, flags, manifestName) {
|
|
|
9007
9007
|
|
|
9008
9008
|
async function workforceStatusCmd(req, flags, manifestName) {
|
|
9009
9009
|
const logger = getLogger();
|
|
9010
|
-
const json = coerceBool(flags?.json, false);
|
|
9010
|
+
const json = flags?.json === false ? false : (coerceBool(flags?.json, false) || hostOutputIsJson());
|
|
9011
9011
|
const manifest = readWorkforceManifestStrict(manifestName);
|
|
9012
9012
|
const { running, reachable, workers: live } = await fetchSupervisorWorkers();
|
|
9013
9013
|
// When the daemon is up but its status socket can't be reached, `live` is
|
|
@@ -10958,6 +10958,7 @@ export {
|
|
|
10958
10958
|
formatWorkforceStatus,
|
|
10959
10959
|
workforceManifestName,
|
|
10960
10960
|
lastManifestValue,
|
|
10961
|
+
hostOutputIsJson,
|
|
10961
10962
|
};
|
|
10962
10963
|
|
|
10963
10964
|
export const metadata = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "c8ctl-plugin-nano",
|
|
3
|
-
"version": "1.44.
|
|
3
|
+
"version": "1.44.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "c8ctl plugin to start, inspect, and stop a local Nano BPM (nanobpmn) cluster",
|
|
6
6
|
"main": "c8ctl-plugin.js",
|
|
@@ -52,17 +52,17 @@
|
|
|
52
52
|
"semantic-release": "^25.0.3"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@nanobpm/agentic": "^0.
|
|
56
|
-
"@nanobpm/urban-agent-client": "^0.1.
|
|
55
|
+
"@nanobpm/agentic": "^0.10.0",
|
|
56
|
+
"@nanobpm/urban-agent-client": "^0.1.10"
|
|
57
57
|
},
|
|
58
58
|
"optionalDependencies": {
|
|
59
59
|
"node-pty": "^1.0.0",
|
|
60
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.44.
|
|
61
|
-
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.44.
|
|
62
|
-
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.44.
|
|
63
|
-
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.44.
|
|
64
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.44.
|
|
65
|
-
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.44.
|
|
66
|
-
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.44.
|
|
60
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.44.3",
|
|
61
|
+
"@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.44.3",
|
|
62
|
+
"@nanobpm/c8ctl-plugin-nano-linux-x64": "1.44.3",
|
|
63
|
+
"@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.44.3",
|
|
64
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.44.3",
|
|
65
|
+
"@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.44.3",
|
|
66
|
+
"@nanobpm/c8ctl-plugin-nano-win32-x64": "1.44.3"
|
|
67
67
|
}
|
|
68
68
|
}
|