c8ctl-plugin-nano 1.62.1 → 1.62.2

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.
Files changed (2) hide show
  1. package/agent-instance.mjs +35 -3
  2. package/package.json +8 -8
@@ -311,6 +311,23 @@ function shortHash(text) {
311
311
  return (h >>> 0).toString(36);
312
312
  }
313
313
 
314
+ // #247: the per-ACTIVATION namespace folded into every content-derived historyItemId
315
+ // so a resumed harness (which restarts its ACP message / tool-call numbering) does not
316
+ // collide with — and get silently deduplicated against — the prior activation's turns.
317
+ // The activation identity is the lease token (a fresh token per `activate jobs`), else
318
+ // the jobKey, else the elementInstanceKey. It is HASHED — the lease token is a
319
+ // secret-ish fence token that must never be embedded raw in a persisted id — and stays
320
+ // STABLE for the life of one activation, so an at-least-once redelivery still dedups
321
+ // within the activation while a genuine resume (new lease) gets a distinct namespace.
322
+ // Returns '' when no identity is available, leaving the id un-namespaced (unchanged).
323
+ export function activationNamespace(job = {}) {
324
+ const leaseToken = job?.leaseToken != null ? String(job.leaseToken) : '';
325
+ const jobKey = job?.jobKey != null ? String(job.jobKey) : '';
326
+ const elementInstanceKey = job?.elementInstanceKey != null ? String(job.elementInstanceKey) : '';
327
+ const token = leaseToken || jobKey || elementInstanceKey || '';
328
+ return token ? shortHash(token) : '';
329
+ }
330
+
314
331
  // Extract per-call metrics from an ACP update when the agent carries them (many
315
332
  // ACP agents do not — a documented ACP fidelity gap — so this is usually absent).
316
333
  // Reads the common usage locations and maps to the AgentHistory item metric field
@@ -408,6 +425,21 @@ export function createAgentInstanceProducer(opts = {}) {
408
425
  const elementInstanceKey = job?.elementInstanceKey != null ? String(job.elementInstanceKey) : '';
409
426
  const elementId = job?.elementId != null ? String(job.elementId) : null;
410
427
  const processInstanceKey = job?.processInstanceKey != null ? String(job.processInstanceKey) : '';
428
+ // #247: namespace every content-derived historyItemId per ACTIVATION. When a parked
429
+ // process resumes, a fresh (cold-started) harness re-activates the SAME AgentInstance
430
+ // and generally RESTARTS its ACP message / tool-call numbering (m-1, call-1, …). The
431
+ // engine dedups appends by historyItemId, so without a per-activation namespace those
432
+ // continuation turns collide with the PRIOR activation's ids and are silently dropped
433
+ // as stale history — the transcript never advances, and the next reactivation replays
434
+ // the same side effects (the failure mode #239 exists to prevent). The namespace is
435
+ // derived from the job's ACTIVATION identity (see `activationNamespace`): stable for
436
+ // the life of this producer, so an at-least-once redelivery of the SAME activation
437
+ // still dedups correctly, while a genuine resume (new lease → new namespace) appends
438
+ // instead of colliding. The CONFIGURATION turn is left keyed on the elementInstanceKey
439
+ // ALONE (stable across activations) so its single header dedups per instance rather
440
+ // than duplicating on every resume.
441
+ const activationNs = activationNamespace(job);
442
+ const nsHistoryId = (id) => (activationNs ? `${activationNs}:${id}` : id);
411
443
  // #229 cross-channel correlation, compact form. Stamps job/eik/pik so the
412
444
  // AgentInstance channel can be joined to the job / relay / git channels. This is the
413
445
  // terse rendering used by the observability lines; `correlation()` below is the
@@ -637,7 +669,7 @@ export function createAgentInstanceProducer(opts = {}) {
637
669
  if (!hasText && !hasMetrics) return;
638
670
  const idBasis = isNonBlank(msg.messageId) ? String(msg.messageId) : `h:${shortHash(text)}`;
639
671
  const turn = {
640
- historyItemId: `${msg.role.toLowerCase()}:${idBasis}`,
672
+ historyItemId: nsHistoryId(`${msg.role.toLowerCase()}:${idBasis}`),
641
673
  loopIteration: msg.loopIteration,
642
674
  role: msg.role,
643
675
  content: hasText ? [{ contentType: 'TEXT', text }] : [],
@@ -653,7 +685,7 @@ export function createAgentInstanceProducer(opts = {}) {
653
685
  flushMessage();
654
686
  if (isNonBlank(c.name)) toolNames.set(String(c.callId), String(c.name));
655
687
  const turn = {
656
- historyItemId: `toolcall:${c.callId}`,
688
+ historyItemId: nsHistoryId(`toolcall:${c.callId}`),
657
689
  loopIteration,
658
690
  role: 'ASSISTANT',
659
691
  content: [],
@@ -673,7 +705,7 @@ export function createAgentInstanceProducer(opts = {}) {
673
705
  const onToolResult = (c) => {
674
706
  flushMessage();
675
707
  const turn = {
676
- historyItemId: `toolresult:${c.callId}`,
708
+ historyItemId: nsHistoryId(`toolresult:${c.callId}`),
677
709
  loopIteration,
678
710
  role: 'TOOL_RESULT',
679
711
  content: contentForResult(c.result),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c8ctl-plugin-nano",
3
- "version": "1.62.1",
3
+ "version": "1.62.2",
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",
@@ -74,12 +74,12 @@
74
74
  },
75
75
  "optionalDependencies": {
76
76
  "node-pty": "^1.0.0",
77
- "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.62.1",
78
- "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.62.1",
79
- "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.62.1",
80
- "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.62.1",
81
- "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.62.1",
82
- "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.62.1",
83
- "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.62.1"
77
+ "@nanobpm/c8ctl-plugin-nano-darwin-arm64": "1.62.2",
78
+ "@nanobpm/c8ctl-plugin-nano-darwin-x64": "1.62.2",
79
+ "@nanobpm/c8ctl-plugin-nano-linux-x64": "1.62.2",
80
+ "@nanobpm/c8ctl-plugin-nano-linux-arm64": "1.62.2",
81
+ "@nanobpm/c8ctl-plugin-nano-linux-armv7": "1.62.2",
82
+ "@nanobpm/c8ctl-plugin-nano-linux-armv6": "1.62.2",
83
+ "@nanobpm/c8ctl-plugin-nano-win32-x64": "1.62.2"
84
84
  }
85
85
  }