c8ctl-plugin-nano 1.44.2 → 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 +61 -83
- 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));
|
|
@@ -4165,15 +4170,17 @@ const ACP_MAX_LINE_BYTES = 8 * 1024 * 1024; // 8 MiB
|
|
|
4165
4170
|
|
|
4166
4171
|
// Drive an ACP (Agent Client Protocol) agent over JSON-RPC 2.0 on stdio.
|
|
4167
4172
|
//
|
|
4168
|
-
// This executor drives ACP end-to-end. Each `session/update` is mapped to
|
|
4169
|
-
//
|
|
4170
|
-
//
|
|
4171
|
-
//
|
|
4172
|
-
// `
|
|
4173
|
-
//
|
|
4174
|
-
//
|
|
4175
|
-
//
|
|
4176
|
-
//
|
|
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.
|
|
4177
4184
|
//
|
|
4178
4185
|
// Framing: ACP frames are newline-delimited JSON-RPC 2.0 messages on stdio (one
|
|
4179
4186
|
// compact JSON object per line, `\n`-terminated). We implement a tiny inline
|
|
@@ -4256,9 +4263,9 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4256
4263
|
|
|
4257
4264
|
// Local mirrors of a human text chunk: the --stream spy tee and the byte-
|
|
4258
4265
|
// capped stdout capture (what the result envelope carries). Deliberately does
|
|
4259
|
-
// NOT touch the relay lane, so a
|
|
4260
|
-
// text locally (for the result + spy) WITHOUT also re-emitting raw text
|
|
4261
|
-
// 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.
|
|
4262
4269
|
const captureHuman = (text) => {
|
|
4263
4270
|
if (!text) return;
|
|
4264
4271
|
const buf = Buffer.from(text, 'utf8');
|
|
@@ -4273,8 +4280,8 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4273
4280
|
// use: the relay tap (framed + jobKey-tagged by the caller) and the local
|
|
4274
4281
|
// --stream spy tee. Byte-capped like the raw captures. This is the minimal-
|
|
4275
4282
|
// mode text path — used for stderr, and as the fallback for any session/update
|
|
4276
|
-
// that has no
|
|
4277
|
-
//
|
|
4283
|
+
// that has no canonical transcript mapping (or when the relay exposes no
|
|
4284
|
+
// transcript-chunk seam).
|
|
4278
4285
|
const emitHuman = (text) => {
|
|
4279
4286
|
if (!text) return;
|
|
4280
4287
|
if (relayTap && typeof relayTap.onData === 'function') {
|
|
@@ -4395,75 +4402,45 @@ function spawnCaptureAcp({ command, args = [], cwd, env, stdinData, timeoutMs, i
|
|
|
4395
4402
|
}
|
|
4396
4403
|
};
|
|
4397
4404
|
|
|
4398
|
-
// #110
|
|
4399
|
-
//
|
|
4400
|
-
//
|
|
4401
|
-
//
|
|
4402
|
-
//
|
|
4403
|
-
//
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
// consumers see omitted/optional strings, not coerced nulls. `status`
|
|
4414
|
-
// falls through to the kind's default only when genuinely absent.
|
|
4415
|
-
const toolOf = (u, defaultStatus) => ({
|
|
4416
|
-
id: u.toolCallId ?? undefined,
|
|
4417
|
-
title: u.title ?? undefined,
|
|
4418
|
-
status: u.status ?? defaultStatus ?? undefined,
|
|
4419
|
-
kind: u.kind ?? undefined,
|
|
4420
|
-
});
|
|
4421
|
-
switch (kind) {
|
|
4422
|
-
case 'agent_message_chunk':
|
|
4423
|
-
return { ...base, kind: 'message', role: 'agent', text: acpTextOf(update.content) };
|
|
4424
|
-
case 'agent_thought_chunk':
|
|
4425
|
-
return { ...base, kind: 'thought', role: 'agent', text: acpTextOf(update.content) };
|
|
4426
|
-
case 'user_message_chunk':
|
|
4427
|
-
return { ...base, kind: 'message', role: 'user', text: acpTextOf(update.content) };
|
|
4428
|
-
case 'tool_call':
|
|
4429
|
-
// `text` mirrors the fallback's plain text so the typed envelope stays
|
|
4430
|
-
// self-contained for lightweight renderers (matches the stated contract).
|
|
4431
|
-
return { ...base, kind: 'tool_call', text: describeUpdate(update), tool: toolOf(update, 'pending') };
|
|
4432
|
-
case 'tool_call_update':
|
|
4433
|
-
return { ...base, kind: 'tool_call_update', text: describeUpdate(update), tool: toolOf(update, undefined) };
|
|
4434
|
-
case 'plan':
|
|
4435
|
-
// Carry the actual plan entries (rich cockpit renders them), not a
|
|
4436
|
-
// count — an absent/malformed payload stays `undefined` (omitted).
|
|
4437
|
-
return { ...base, kind: 'plan', entries: Array.isArray(update.entries) ? update.entries : undefined };
|
|
4438
|
-
default:
|
|
4439
|
-
// Unmodelled kind → no typed envelope; caller uses the text fallback.
|
|
4440
|
-
return null;
|
|
4441
|
-
}
|
|
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; }
|
|
4442
4420
|
};
|
|
4443
4421
|
|
|
4444
|
-
// Publish a session/update: prefer the
|
|
4445
|
-
//
|
|
4446
|
-
//
|
|
4447
|
-
// 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).
|
|
4448
4426
|
const emitTranscript = (update) => {
|
|
4449
|
-
const
|
|
4450
|
-
if (
|
|
4451
|
-
// 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.
|
|
4452
4430
|
// If the seam throws (a downstream tap implementation, not just the
|
|
4453
|
-
// built-in best-effort
|
|
4454
|
-
//
|
|
4455
|
-
//
|
|
4456
|
-
// 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.
|
|
4457
4434
|
let published = false;
|
|
4458
|
-
try { relayTap.
|
|
4435
|
+
try { relayTap.relayTranscriptChunk(chunk); published = true; } catch { /* relay best-effort */ }
|
|
4459
4436
|
if (published) {
|
|
4460
4437
|
// Mirror the human text locally (spy tee + captured stdout) so the
|
|
4461
4438
|
// result envelope and --stream spy are unchanged — without re-emitting
|
|
4462
|
-
// raw text onto the relay lane, which now carries the
|
|
4439
|
+
// raw text onto the relay lane, which now carries the canonical chunk.
|
|
4463
4440
|
captureHuman(describeUpdate(update));
|
|
4464
4441
|
return;
|
|
4465
4442
|
}
|
|
4466
|
-
//
|
|
4443
|
+
// Chunk publish threw → fall through to the text lane below.
|
|
4467
4444
|
}
|
|
4468
4445
|
// Fallback: minimal text-chunk path (relay text + spy tee + capture).
|
|
4469
4446
|
emitHuman(describeUpdate(update));
|
|
@@ -4825,17 +4802,18 @@ function runAgentJob(profile, job, opts = {}) {
|
|
|
4825
4802
|
const relayTap = relaySession
|
|
4826
4803
|
? {
|
|
4827
4804
|
onData: (buf) => relaySession.relay(buf),
|
|
4828
|
-
// #110
|
|
4829
|
-
// session/update to
|
|
4830
|
-
//
|
|
4831
|
-
//
|
|
4832
|
-
//
|
|
4833
|
-
//
|
|
4834
|
-
//
|
|
4835
|
-
//
|
|
4836
|
-
//
|
|
4837
|
-
|
|
4838
|
-
|
|
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 */ }
|
|
4839
4817
|
},
|
|
4840
4818
|
attachSteer: (write) => relaySession.attachSteer(write),
|
|
4841
4819
|
}
|
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
|
}
|