impel-cli 0.20.10 → 0.20.12

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/RELEASE_NOTES.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Release notes
2
2
 
3
+ ## 0.20.12 — Internal admission retry
4
+
5
+ - Absorbs brief local run-allocation lock contention inside the transport
6
+ instead of returning a model-visible busy error.
7
+ - Keeps cancellation bounded while preserving the same single persisted
8
+ idempotency key and authoritative run identity.
9
+ - Prevents a transient concurrency collision from adding a second agent tool
10
+ call or wrapper inference turn.
11
+
12
+ ## 0.20.11 — Managed profile upgrade compatibility
13
+
14
+ - Recognizes every discovery-root manifest from v2 through the current schema
15
+ when refreshing generated native agents.
16
+ - Preserves collision protection for files absent from the prior managed
17
+ manifest while allowing v8 profiles to regenerate onto v9.
18
+ - Adds a regression test for upgrading the immediately previous manifest.
19
+
3
20
  ## 0.20.10 — Direct-answer continuation surface
4
21
 
5
22
  - Limits answer-only native agents to the exact answer and resume tools.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.10",
3
+ "version": "0.20.12",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
package/src/agents.js CHANGED
@@ -949,7 +949,9 @@ export function syncAgentProfile({
949
949
  throw new Error("generated native-agent destinations are not unique");
950
950
  }
951
951
  const priorFiles = new Set(prior?.files || []);
952
- const priorUsesDiscoveryRoot = [2, 3, 4, 5, 6, MANAGED_AGENT_MANIFEST_VERSION].includes(prior?.version);
952
+ const priorUsesDiscoveryRoot = Number.isInteger(prior?.version)
953
+ && prior.version >= 2
954
+ && prior.version <= MANAGED_AGENT_MANIFEST_VERSION;
953
955
 
954
956
  // Native clients discover standalone definitions directly under `agents/`.
955
957
  // Preflight every destination before writing so an unmanaged file with the
@@ -52,6 +52,8 @@ const DEFAULT_MAX_RESPONSE_BYTES = 2 * 1024 * 1024;
52
52
  const DEFAULT_MAX_STATE_BYTES = 512 * 1024;
53
53
  const DEFAULT_LOCK_LEASE_MS = 75_000;
54
54
  const DEFAULT_ADMISSION_LEASE_MS = 10_000;
55
+ const DEFAULT_ADMISSION_RETRY_ATTEMPTS = 40;
56
+ const DEFAULT_ADMISSION_RETRY_INTERVAL_MS = 25;
55
57
  const STATE_COMMIT_LEASE_MARGIN_MS = 5_000;
56
58
  const CIBI_START_IN_PROGRESS_CODE = "eve_run_drain_start_in_progress";
57
59
  const CIBI_START_IN_PROGRESS_MESSAGE = "The durable Eve drain workflow is still being enqueued; retry this idempotent start.";
@@ -167,6 +169,30 @@ function throwIfAborted(signal) {
167
169
  if (signal?.aborted) throw abortError();
168
170
  }
169
171
 
172
+ function abortableDelay(ms, signal) {
173
+ throwIfAborted(signal);
174
+ return new Promise((resolve, reject) => {
175
+ let settled = false;
176
+ let timeout = null;
177
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
178
+ const onAbort = () => {
179
+ if (settled) return;
180
+ settled = true;
181
+ clearTimeout(timeout);
182
+ cleanup();
183
+ reject(abortError());
184
+ };
185
+ timeout = setTimeout(() => {
186
+ if (settled) return;
187
+ settled = true;
188
+ cleanup();
189
+ resolve();
190
+ }, ms);
191
+ signal?.addEventListener("abort", onAbort, { once: true });
192
+ if (signal?.aborted) onAbort();
193
+ });
194
+ }
195
+
170
196
  function boundedAttachmentSignal(parentSignal, timeoutMs) {
171
197
  const controller = new AbortController();
172
198
  let timedOut = false;
@@ -1660,11 +1686,15 @@ export class NativeAgentCompositeTransport {
1660
1686
  };
1661
1687
  }
1662
1688
 
1663
- allocateState(startArguments, { startMode = "durable" } = {}) {
1689
+ async allocateState(startArguments, { startMode = "durable", signal } = {}) {
1664
1690
  let admission = null;
1665
1691
  try {
1666
1692
  this.ensureRoots();
1667
- admission = this.acquireAdmissionLock();
1693
+ for (let attempt = 0; attempt < DEFAULT_ADMISSION_RETRY_ATTEMPTS; attempt += 1) {
1694
+ admission = this.acquireAdmissionLock();
1695
+ if (admission) break;
1696
+ await abortableDelay(DEFAULT_ADMISSION_RETRY_INTERVAL_MS, signal);
1697
+ }
1668
1698
  if (!admission) {
1669
1699
  throw new Error("native-agent run allocation is busy; retry without changing the request");
1670
1700
  }
@@ -1726,7 +1756,7 @@ export class NativeAgentCompositeTransport {
1726
1756
  ...(args.context !== undefined ? { context: args.context } : {}),
1727
1757
  contextKeys: args.contextKeys ?? [],
1728
1758
  }, prepared.agent);
1729
- state = this.allocateState(startArguments, { startMode: "answer" });
1759
+ state = await this.allocateState(startArguments, { startMode: "answer", signal: attachment.signal });
1730
1760
  lock = this.acquireLock(state.invocationId);
1731
1761
  if (!lock) return handleForState(state);
1732
1762
  const result = await this.attach(state, prepared, {
@@ -1770,7 +1800,7 @@ export class NativeAgentCompositeTransport {
1770
1800
  try {
1771
1801
  const prepared = await this.prepareNewSession(attachment.signal);
1772
1802
  const startArguments = this.normalizeRunArguments(args, prepared.agent);
1773
- state = this.allocateState(startArguments);
1803
+ state = await this.allocateState(startArguments, { signal: attachment.signal });
1774
1804
  // The durable identity and exact retry arguments exist before the first
1775
1805
  // upstream byte is sent.
1776
1806
  lock = this.acquireLock(state.invocationId);