impel-cli 0.20.13 → 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,15 @@
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
+
3
13
  ## 0.20.13 — Allocation publication race
4
14
 
5
15
  - Retries allocation when a competing process moves the published lock between
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "impel-cli",
3
- "version": "0.20.13",
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",
@@ -1564,6 +1579,18 @@ export class NativeAgentCompositeTransport {
1564
1579
  }
1565
1580
 
1566
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
+ }
1567
1594
  const expected = {
1568
1595
  tenantId: this.tenantId,
1569
1596
  agentId: this.agentId,
@@ -1780,7 +1807,9 @@ export class NativeAgentCompositeTransport {
1780
1807
  runId: state.upstreamRunId,
1781
1808
  };
1782
1809
  }
1783
- return result;
1810
+ return result?.schema === NATIVE_AGENT_HANDLE_SCHEMA
1811
+ ? continuationForState(state)
1812
+ : result;
1784
1813
  } catch (error) {
1785
1814
  if (!state) throw error;
1786
1815
  if (lock) {
@@ -1790,7 +1819,7 @@ export class NativeAgentCompositeTransport {
1790
1819
  // The persisted handle remains authoritative after a lost lease.
1791
1820
  }
1792
1821
  }
1793
- return handleForState(state);
1822
+ return continuationForState(state);
1794
1823
  } finally {
1795
1824
  lock?.release();
1796
1825
  attachment.dispose();
@@ -1811,11 +1840,14 @@ export class NativeAgentCompositeTransport {
1811
1840
  // upstream byte is sent.
1812
1841
  lock = this.acquireLock(state.invocationId);
1813
1842
  if (!lock) return handleForState(state);
1814
- return await this.attach(state, prepared, {
1843
+ const result = await this.attach(state, prepared, {
1815
1844
  signal: attachment.signal,
1816
1845
  onProgress,
1817
1846
  lock,
1818
1847
  });
1848
+ return state.startMode === "answer" && result?.schema === NATIVE_AGENT_HANDLE_SCHEMA
1849
+ ? continuationForState(state)
1850
+ : result;
1819
1851
  } catch (error) {
1820
1852
  // Once state exists, cancellation or the local attachment deadline must
1821
1853
  // hand the caller the only handle that can resume it.
@@ -1834,7 +1866,7 @@ export class NativeAgentCompositeTransport {
1834
1866
  exactObject(args, ["handle"], "resume_native_agent_run arguments");
1835
1867
  if (!Object.hasOwn(args, "handle")) throw new Error("resume_native_agent_run requires a handle");
1836
1868
  const handle = normalizeHandle(args.handle);
1837
- if (handle.tenantId !== this.tenantId) {
1869
+ if (handle.schema === NATIVE_AGENT_HANDLE_SCHEMA && handle.tenantId !== this.tenantId) {
1838
1870
  throw new Error("native-agent run handle does not match this fixed tenant");
1839
1871
  }
1840
1872
  this.ensureRoots();
@@ -2130,7 +2162,7 @@ export class NativeAgentCompositeTransport {
2130
2162
  result = resultForState(state);
2131
2163
  if (result.schema === NATIVE_AGENT_RESULT_SCHEMA) return result;
2132
2164
  }
2133
- return handleForState(state);
2165
+ return state.startMode === "answer" ? continuationForState(state) : handleForState(state);
2134
2166
  } catch (error) {
2135
2167
  if (!state.upstreamRunId) throw error;
2136
2168
  try {
@@ -2167,6 +2199,19 @@ const HANDLE_SCHEMA = {
2167
2199
  },
2168
2200
  };
2169
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
+
2170
2215
  export function nativeAgentCompositeTools({ mode = "durable" } = {}) {
2171
2216
  if (!["durable", "recovery", "answer"].includes(mode)) throw new Error("invalid native-agent MCP mode");
2172
2217
  const answerTool = {
@@ -2233,7 +2278,14 @@ export function nativeAgentCompositeTools({ mode = "durable" } = {}) {
2233
2278
  // continuation handle. Once that handle exists, resume is the only valid
2234
2279
  // next action; exposing recovery here gives the wrapper model an
2235
2280
  // unnecessary branch and can add a fifth inference round to healthy runs.
2236
- 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
+ }];
2237
2289
  }
2238
2290
  return mode === "recovery" ? tools.filter(({ name }) => name !== NATIVE_AGENT_RUN_TOOL) : tools;
2239
2291
  }