impel-cli 0.20.12 → 0.20.14

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,23 @@
1
1
  # Release notes
2
2
 
3
+ ## 0.20.14 — Opaque direct-answer continuations
4
+
5
+ - Returns a minimal local continuation locator for read-only direct answers,
6
+ so wrapper models no longer need to copy policy or request fingerprints.
7
+ - Resolves the locator against integrity-validated tenant-local state and the
8
+ MCP server's fixed agent binding; durable and write-capable handles retain
9
+ their full fail-closed validation.
10
+ - Adds a regression test for the production case where Claude shortened a
11
+ request fingerprint while preserving the invocation id.
12
+
13
+ ## 0.20.13 — Allocation publication race
14
+
15
+ - Retries allocation when a competing process moves the published lock between
16
+ the expected create collision and inspection.
17
+ - Prevents a harmless `ENOENT` publication race from escaping as an MCP error
18
+ or consuming an agent inference turn.
19
+ - Adds a deterministic regression test for the exact production interleaving.
20
+
3
21
  ## 0.20.12 — Internal admission retry
4
22
 
5
23
  - Absorbs brief local run-allocation lock contention inside the transport
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.12",
3
+ "version": "0.20.14",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
@@ -31,6 +31,7 @@ export {
31
31
  NATIVE_AGENT_RUN_TOOL,
32
32
  };
33
33
  export const NATIVE_AGENT_HANDLE_SCHEMA = "impel.native-agent-run.v1";
34
+ export const NATIVE_AGENT_CONTINUATION_SCHEMA = "impel.native-agent-continuation.v1";
34
35
  export const NATIVE_AGENT_RESULT_SCHEMA = "impel.native-agent-result.v1";
35
36
  export const NATIVE_AGENT_RECOVERY_SCHEMA = "impel.native-agent-recovery.v1";
36
37
 
@@ -390,7 +391,21 @@ function handleForState(state) {
390
391
  };
391
392
  }
392
393
 
394
+ function continuationForState(state) {
395
+ return {
396
+ schema: NATIVE_AGENT_CONTINUATION_SCHEMA,
397
+ invocationId: state.invocationId,
398
+ };
399
+ }
400
+
393
401
  function normalizeHandle(value) {
402
+ if (value?.schema === NATIVE_AGENT_CONTINUATION_SCHEMA) {
403
+ exactObject(value, ["schema", "invocationId"], "native-agent continuation");
404
+ if (typeof value.invocationId !== "string" || !SAFE_INVOCATION_RE.test(value.invocationId)) {
405
+ throw new Error("native-agent continuation invocationId is invalid");
406
+ }
407
+ return { ...value };
408
+ }
394
409
  exactObject(value, [
395
410
  "schema",
396
411
  "tenantId",
@@ -1388,7 +1403,13 @@ export class NativeAgentCompositeTransport {
1388
1403
  throw error;
1389
1404
  }
1390
1405
  fs.rmSync(generationPath, { recursive: true, force: true });
1391
- const stat = fs.lstatSync(lockPath);
1406
+ let stat;
1407
+ try {
1408
+ stat = fs.lstatSync(lockPath);
1409
+ } catch (statError) {
1410
+ if (statError?.code === "ENOENT") continue;
1411
+ throw statError;
1412
+ }
1392
1413
  if (stat.isSymbolicLink()) throw new Error("refusing to use symlinked native-agent run lock");
1393
1414
  let current = null;
1394
1415
  let legacy = null;
@@ -1558,6 +1579,18 @@ export class NativeAgentCompositeTransport {
1558
1579
  }
1559
1580
 
1560
1581
  validateBoundState(state, handle) {
1582
+ if (handle.schema === NATIVE_AGENT_CONTINUATION_SCHEMA) {
1583
+ if (state.startMode !== "answer") {
1584
+ throw new Error("native-agent continuation does not identify a direct-answer run");
1585
+ }
1586
+ if (state.tenantId !== this.tenantId
1587
+ || state.agentId !== this.agentId
1588
+ || state.scopeParam !== this.scopeParam
1589
+ || state.policyFingerprint !== this.policyFingerprint) {
1590
+ throw new Error("native-agent continuation does not match this fixed binding");
1591
+ }
1592
+ return;
1593
+ }
1561
1594
  const expected = {
1562
1595
  tenantId: this.tenantId,
1563
1596
  agentId: this.agentId,
@@ -1774,7 +1807,9 @@ export class NativeAgentCompositeTransport {
1774
1807
  runId: state.upstreamRunId,
1775
1808
  };
1776
1809
  }
1777
- return result;
1810
+ return result?.schema === NATIVE_AGENT_HANDLE_SCHEMA
1811
+ ? continuationForState(state)
1812
+ : result;
1778
1813
  } catch (error) {
1779
1814
  if (!state) throw error;
1780
1815
  if (lock) {
@@ -1784,7 +1819,7 @@ export class NativeAgentCompositeTransport {
1784
1819
  // The persisted handle remains authoritative after a lost lease.
1785
1820
  }
1786
1821
  }
1787
- return handleForState(state);
1822
+ return continuationForState(state);
1788
1823
  } finally {
1789
1824
  lock?.release();
1790
1825
  attachment.dispose();
@@ -1805,11 +1840,14 @@ export class NativeAgentCompositeTransport {
1805
1840
  // upstream byte is sent.
1806
1841
  lock = this.acquireLock(state.invocationId);
1807
1842
  if (!lock) return handleForState(state);
1808
- return await this.attach(state, prepared, {
1843
+ const result = await this.attach(state, prepared, {
1809
1844
  signal: attachment.signal,
1810
1845
  onProgress,
1811
1846
  lock,
1812
1847
  });
1848
+ return state.startMode === "answer" && result?.schema === NATIVE_AGENT_HANDLE_SCHEMA
1849
+ ? continuationForState(state)
1850
+ : result;
1813
1851
  } catch (error) {
1814
1852
  // Once state exists, cancellation or the local attachment deadline must
1815
1853
  // hand the caller the only handle that can resume it.
@@ -1828,7 +1866,7 @@ export class NativeAgentCompositeTransport {
1828
1866
  exactObject(args, ["handle"], "resume_native_agent_run arguments");
1829
1867
  if (!Object.hasOwn(args, "handle")) throw new Error("resume_native_agent_run requires a handle");
1830
1868
  const handle = normalizeHandle(args.handle);
1831
- if (handle.tenantId !== this.tenantId) {
1869
+ if (handle.schema === NATIVE_AGENT_HANDLE_SCHEMA && handle.tenantId !== this.tenantId) {
1832
1870
  throw new Error("native-agent run handle does not match this fixed tenant");
1833
1871
  }
1834
1872
  this.ensureRoots();
@@ -2124,7 +2162,7 @@ export class NativeAgentCompositeTransport {
2124
2162
  result = resultForState(state);
2125
2163
  if (result.schema === NATIVE_AGENT_RESULT_SCHEMA) return result;
2126
2164
  }
2127
- return handleForState(state);
2165
+ return state.startMode === "answer" ? continuationForState(state) : handleForState(state);
2128
2166
  } catch (error) {
2129
2167
  if (!state.upstreamRunId) throw error;
2130
2168
  try {
@@ -2161,6 +2199,19 @@ const HANDLE_SCHEMA = {
2161
2199
  },
2162
2200
  };
2163
2201
 
2202
+ const ANSWER_RESUME_SCHEMA = {
2203
+ type: "object",
2204
+ additionalProperties: false,
2205
+ required: ["schema", "invocationId"],
2206
+ properties: {
2207
+ ...HANDLE_SCHEMA.properties,
2208
+ schema: {
2209
+ type: "string",
2210
+ enum: [NATIVE_AGENT_CONTINUATION_SCHEMA, NATIVE_AGENT_HANDLE_SCHEMA],
2211
+ },
2212
+ },
2213
+ };
2214
+
2164
2215
  export function nativeAgentCompositeTools({ mode = "durable" } = {}) {
2165
2216
  if (!["durable", "recovery", "answer"].includes(mode)) throw new Error("invalid native-agent MCP mode");
2166
2217
  const answerTool = {
@@ -2227,7 +2278,14 @@ export function nativeAgentCompositeTools({ mode = "durable" } = {}) {
2227
2278
  // continuation handle. Once that handle exists, resume is the only valid
2228
2279
  // next action; exposing recovery here gives the wrapper model an
2229
2280
  // unnecessary branch and can add a fifth inference round to healthy runs.
2230
- return [answerTool, tools.find(({ name }) => name === NATIVE_AGENT_RESUME_TOOL)];
2281
+ const resumeTool = tools.find(({ name }) => name === NATIVE_AGENT_RESUME_TOOL);
2282
+ return [answerTool, {
2283
+ ...resumeTool,
2284
+ inputSchema: {
2285
+ ...resumeTool.inputSchema,
2286
+ properties: { handle: ANSWER_RESUME_SCHEMA },
2287
+ },
2288
+ }];
2231
2289
  }
2232
2290
  return mode === "recovery" ? tools.filter(({ name }) => name !== NATIVE_AGENT_RUN_TOOL) : tools;
2233
2291
  }