impel-cli 0.20.36 → 0.20.37
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 +9 -0
- package/package.json +1 -1
- package/src/agents.js +1 -1
- package/src/nativeAgentTransport.js +86 -9
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.37 — Resume ambiguous native answers
|
|
4
|
+
|
|
5
|
+
- Retries Next's typed ambiguous direct-answer response with the exact persisted
|
|
6
|
+
idempotency key, resuming the existing Eve answer instead of duplicating it.
|
|
7
|
+
- Keeps retries bounded and cancellation-safe while rejecting incomplete or
|
|
8
|
+
untyped retry claims as definitive failures.
|
|
9
|
+
- Preserves strict continuation handles for answer-only adapters and full run
|
|
10
|
+
handles for recovery-only adapters across every nonterminal resume.
|
|
11
|
+
|
|
3
12
|
## 0.20.36 — Restore Codex execution
|
|
4
13
|
|
|
5
14
|
- Keeps Codex 0.147's stable code-mode host enabled instead of overriding it
|
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -696,7 +696,7 @@ function retiredAdapterInstructions(tenantId, agent) {
|
|
|
696
696
|
`You are a recovery-only transport adapter for retired Impel native-agent binding ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
|
|
697
697
|
`Collaboration tools are unavailable for this adapter. Never call send_message, followup_task, interrupt_agent, spawn_agent, or wait_agent. Return one final response to the caller only after the native-agent transport reaches terminal.`,
|
|
698
698
|
`You cannot start new work. Never call run_native_agent and never perform or recreate the assigned task yourself.`,
|
|
699
|
-
`Call ${nativeToolName(NATIVE_AGENT_RECOVER_TOOL)} with {} to obtain integrity-validated pending handles for this fixed binding, then call ${nativeToolName(NATIVE_AGENT_RESUME_TOOL)} with the intended handle
|
|
699
|
+
`Call ${nativeToolName(NATIVE_AGENT_RECOVER_TOOL)} with {} to obtain integrity-validated ${JSON.stringify("impel.native-agent-run.v1")} pending handles for this fixed binding, then call ${nativeToolName(NATIVE_AGENT_RESUME_TOOL)} with the intended complete handle. Every nonterminal resume returns the same handle schema; pass that complete returned handle unchanged to the next resume until terminal. If multiple handles cannot be safely associated with the request, report them instead of choosing.`,
|
|
700
700
|
`Return successful finalText byte-for-byte. For failure, return the preserved runId, output, and error without inventing a replacement result.`,
|
|
701
701
|
].join(" ");
|
|
702
702
|
}
|
|
@@ -65,6 +65,7 @@ const CIBI_START_IN_PROGRESS_MESSAGE = "The durable Eve drain workflow is still
|
|
|
65
65
|
const CTOS_START_TIMEOUT_MESSAGE = `MCP tool call failed for ${NATIVE_AGENT_START_TOOL}: The operation was aborted due to timeout: mcp tool call failed`;
|
|
66
66
|
const MANAGED_VIRTUAL_KEY_MISS_MESSAGE = "virtual key not found. The provided virtual key does not exist or has been revoked.";
|
|
67
67
|
const MANAGED_VIRTUAL_KEY_MISS_RETRY_DELAY_MS = 250;
|
|
68
|
+
const NEXT_AMBIGUOUS_ANSWER_CODE = "native_agent_answer_ambiguous";
|
|
68
69
|
const MCP_TOOL_RESULT_ERROR = Symbol("native-agent MCP tool result error");
|
|
69
70
|
const TERMINAL_STATUSES = new Set(["succeeded", "failed", "cancelled", "canceled"]);
|
|
70
71
|
|
|
@@ -332,6 +333,7 @@ function readPrivateState(filePath, maxBytes) {
|
|
|
332
333
|
"idempotencyKey",
|
|
333
334
|
"startArguments",
|
|
334
335
|
"upstreamRunId",
|
|
336
|
+
"answerRunId",
|
|
335
337
|
"status",
|
|
336
338
|
"startAttempts",
|
|
337
339
|
"startMode",
|
|
@@ -365,6 +367,13 @@ function readPrivateState(filePath, maxBytes) {
|
|
|
365
367
|
|| (state.fence !== undefined && (!Number.isSafeInteger(state.fence) || state.fence < 0))
|
|
366
368
|
|| (state.sequence !== undefined && (!Number.isSafeInteger(state.sequence) || state.sequence < 0))
|
|
367
369
|
|| (state.version !== undefined && (!Number.isSafeInteger(state.version) || state.version < 0))
|
|
370
|
+
|| (state.answerRunId !== undefined
|
|
371
|
+
&& (state.startMode !== "answer"
|
|
372
|
+
|| state.startAttempts < 1
|
|
373
|
+
|| state.upstreamRunId !== null
|
|
374
|
+
|| typeof state.answerRunId !== "string"
|
|
375
|
+
|| !state.answerRunId.trim()
|
|
376
|
+
|| state.answerRunId.length > 512))
|
|
368
377
|
|| (state.upstreamRunId !== null && (typeof state.upstreamRunId !== "string" || !state.upstreamRunId))) {
|
|
369
378
|
throw new Error("invalid field");
|
|
370
379
|
}
|
|
@@ -417,6 +426,22 @@ function continuationForState(state) {
|
|
|
417
426
|
};
|
|
418
427
|
}
|
|
419
428
|
|
|
429
|
+
function attachmentHandleForState(state) {
|
|
430
|
+
return state.startMode === "answer"
|
|
431
|
+
? continuationForState(state)
|
|
432
|
+
: handleForState(state);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function resumedHandleForState(state, incomingHandle) {
|
|
436
|
+
// The strict MCP surfaces use disjoint handle families: answer-only accepts
|
|
437
|
+
// continuation.v1, while recovery-only accepts run.v1. Preserve the
|
|
438
|
+
// validated incoming family across every nonterminal resume so either
|
|
439
|
+
// surface can continue the same persisted direct-answer state.
|
|
440
|
+
return incomingHandle.schema === NATIVE_AGENT_HANDLE_SCHEMA
|
|
441
|
+
? handleForState(state)
|
|
442
|
+
: attachmentHandleForState(state);
|
|
443
|
+
}
|
|
444
|
+
|
|
420
445
|
function normalizeHandle(value) {
|
|
421
446
|
if (value?.schema === NATIVE_AGENT_CONTINUATION_SCHEMA) {
|
|
422
447
|
exactObject(value, ["schema", "invocationId"], "native-agent continuation");
|
|
@@ -642,6 +667,24 @@ function retryableManagedVirtualKeyMiss(error) {
|
|
|
642
667
|
&& error.message.includes(MANAGED_VIRTUAL_KEY_MISS_MESSAGE);
|
|
643
668
|
}
|
|
644
669
|
|
|
670
|
+
function retryableNativeAgentAnswerRunId(error) {
|
|
671
|
+
const data = error?.data;
|
|
672
|
+
if (!(error instanceof NativeAgentUpstreamError)
|
|
673
|
+
|| error.code !== -32000
|
|
674
|
+
|| !data
|
|
675
|
+
|| typeof data !== "object"
|
|
676
|
+
|| Array.isArray(data)
|
|
677
|
+
|| data.code !== NEXT_AMBIGUOUS_ANSWER_CODE
|
|
678
|
+
|| data.retryable !== true
|
|
679
|
+
|| data.sameIdempotencyKeyRequired !== true
|
|
680
|
+
|| typeof data.runId !== "string"
|
|
681
|
+
|| !data.runId.trim()
|
|
682
|
+
|| data.runId.length > 512) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
return data.runId;
|
|
686
|
+
}
|
|
687
|
+
|
|
645
688
|
export class NativeAgentUpstreamSession {
|
|
646
689
|
constructor({
|
|
647
690
|
gatewayUrl,
|
|
@@ -792,6 +835,14 @@ export class NativeAgentUpstreamSession {
|
|
|
792
835
|
&& error.message === CTOS_START_TIMEOUT_MESSAGE))) {
|
|
793
836
|
error.ambiguous = true;
|
|
794
837
|
}
|
|
838
|
+
if (name === NATIVE_AGENT_UPSTREAM_ANSWER_TOOL
|
|
839
|
+
&& retryableNativeAgentAnswerRunId(error) !== null) {
|
|
840
|
+
// Next has confirmed that the direct-answer ledger remains live and
|
|
841
|
+
// that only an exact idempotent replay may resume it. Keep generic
|
|
842
|
+
// JSON-RPC errors definitive; this branch requires the complete
|
|
843
|
+
// typed resume contract.
|
|
844
|
+
error.ambiguous = true;
|
|
845
|
+
}
|
|
795
846
|
throw error;
|
|
796
847
|
}
|
|
797
848
|
}
|
|
@@ -1826,7 +1877,7 @@ export class NativeAgentCompositeTransport {
|
|
|
1826
1877
|
}, prepared.agent);
|
|
1827
1878
|
state = await this.allocateState(startArguments, { startMode: "answer", signal: attachment.signal });
|
|
1828
1879
|
lock = this.acquireLock(state.invocationId);
|
|
1829
|
-
if (!lock) return
|
|
1880
|
+
if (!lock) return continuationForState(state);
|
|
1830
1881
|
const result = await this.attach(state, prepared, {
|
|
1831
1882
|
signal: attachment.signal,
|
|
1832
1883
|
lock,
|
|
@@ -1936,13 +1987,18 @@ export class NativeAgentCompositeTransport {
|
|
|
1936
1987
|
const prepared = state.upstreamRunId
|
|
1937
1988
|
? await this.prepareExistingSession(attachment.signal)
|
|
1938
1989
|
: await this.preparePersistedStartSession(attachment.signal, state);
|
|
1939
|
-
|
|
1990
|
+
const resumed = await this.attach(state, prepared, {
|
|
1940
1991
|
signal: attachment.signal,
|
|
1941
1992
|
onProgress,
|
|
1942
1993
|
lock,
|
|
1943
1994
|
});
|
|
1995
|
+
return resumed?.schema === NATIVE_AGENT_RESULT_SCHEMA
|
|
1996
|
+
? resumed
|
|
1997
|
+
: resumedHandleForState(state, handle);
|
|
1944
1998
|
} catch (error) {
|
|
1945
|
-
if (error?.name === "AbortError")
|
|
1999
|
+
if (error?.name === "AbortError") {
|
|
2000
|
+
return validated ? resumedHandleForState(state, handle) : handle;
|
|
2001
|
+
}
|
|
1946
2002
|
if (validated && !state?.upstreamRunId && error?.code === "LOCAL_POLICY_CHANGED_BEFORE_START") {
|
|
1947
2003
|
this.compactTerminalFailure(
|
|
1948
2004
|
state,
|
|
@@ -1959,7 +2015,7 @@ export class NativeAgentCompositeTransport {
|
|
|
1959
2015
|
} catch {
|
|
1960
2016
|
// Never write after losing ownership of the bounded lease.
|
|
1961
2017
|
}
|
|
1962
|
-
return handle;
|
|
2018
|
+
return resumedHandleForState(state, handle);
|
|
1963
2019
|
}
|
|
1964
2020
|
throw error;
|
|
1965
2021
|
} finally {
|
|
@@ -2078,6 +2134,7 @@ export class NativeAgentCompositeTransport {
|
|
|
2078
2134
|
}
|
|
2079
2135
|
const runId = boundedString(payload.runId, "native-agent runId", 512);
|
|
2080
2136
|
const next = structuredClone(state);
|
|
2137
|
+
delete next.answerRunId;
|
|
2081
2138
|
next.upstreamRunId = runId;
|
|
2082
2139
|
next.status = typeof payload.status === "string"
|
|
2083
2140
|
&& payload.status.length <= 160
|
|
@@ -2151,10 +2208,22 @@ export class NativeAgentCompositeTransport {
|
|
|
2151
2208
|
break;
|
|
2152
2209
|
} catch (error) {
|
|
2153
2210
|
if (error?.name === "AbortError") throw error;
|
|
2211
|
+
const answerRunId = state.startMode === "answer"
|
|
2212
|
+
? retryableNativeAgentAnswerRunId(error)
|
|
2213
|
+
: null;
|
|
2214
|
+
if (answerRunId !== null) {
|
|
2215
|
+
if (state.answerRunId && state.answerRunId !== answerRunId) {
|
|
2216
|
+
this.persistDiagnostic(state, new Error(
|
|
2217
|
+
"native-agent answer retry contract returned a different runId",
|
|
2218
|
+
), lock);
|
|
2219
|
+
return attachmentHandleForState(state);
|
|
2220
|
+
}
|
|
2221
|
+
state.answerRunId = answerRunId;
|
|
2222
|
+
}
|
|
2154
2223
|
if (!error?.ambiguous) {
|
|
2155
2224
|
if (startOutcomeUncertain) {
|
|
2156
2225
|
this.persistDiagnostic(state, error, lock);
|
|
2157
|
-
return
|
|
2226
|
+
return attachmentHandleForState(state);
|
|
2158
2227
|
}
|
|
2159
2228
|
this.compactTerminalFailure(
|
|
2160
2229
|
state,
|
|
@@ -2169,7 +2238,15 @@ export class NativeAgentCompositeTransport {
|
|
|
2169
2238
|
this.persistDiagnostic(state, error, lock);
|
|
2170
2239
|
}
|
|
2171
2240
|
}
|
|
2172
|
-
if (!started) return
|
|
2241
|
+
if (!started) return attachmentHandleForState(state);
|
|
2242
|
+
if (state.startMode === "answer"
|
|
2243
|
+
&& state.answerRunId
|
|
2244
|
+
&& started.runId !== state.answerRunId) {
|
|
2245
|
+
this.persistDiagnostic(state, new Error(
|
|
2246
|
+
"native-agent answer retry returned a different runId",
|
|
2247
|
+
), lock);
|
|
2248
|
+
return attachmentHandleForState(state);
|
|
2249
|
+
}
|
|
2173
2250
|
|
|
2174
2251
|
// Persist the upstream identity before accepting any optional or large
|
|
2175
2252
|
// response fields. From this point every attachment failure is safely
|
|
@@ -2184,7 +2261,7 @@ export class NativeAgentCompositeTransport {
|
|
|
2184
2261
|
for (let poll = 0; poll < this.maxPolls; poll += 1) {
|
|
2185
2262
|
throwIfAborted(signal);
|
|
2186
2263
|
const remainingMs = deadline - this.now();
|
|
2187
|
-
if (remainingMs <= 1_500) return
|
|
2264
|
+
if (remainingMs <= 1_500) return attachmentHandleForState(state);
|
|
2188
2265
|
const boundedWait = Math.max(1, Math.min(waitSeconds, Math.floor((remainingMs - 1_000) / 1000)));
|
|
2189
2266
|
const observed = await session.call(NATIVE_AGENT_READ_TOOL, {
|
|
2190
2267
|
runId: state.upstreamRunId,
|
|
@@ -2197,7 +2274,7 @@ export class NativeAgentCompositeTransport {
|
|
|
2197
2274
|
result = resultForState(state);
|
|
2198
2275
|
if (result.schema === NATIVE_AGENT_RESULT_SCHEMA) return result;
|
|
2199
2276
|
}
|
|
2200
|
-
return
|
|
2277
|
+
return attachmentHandleForState(state);
|
|
2201
2278
|
} catch (error) {
|
|
2202
2279
|
if (!state.upstreamRunId) throw error;
|
|
2203
2280
|
try {
|
|
@@ -2206,7 +2283,7 @@ export class NativeAgentCompositeTransport {
|
|
|
2206
2283
|
// A lost lease forbids stale writes. The already-persisted handle is
|
|
2207
2284
|
// still the only safe return value.
|
|
2208
2285
|
}
|
|
2209
|
-
return
|
|
2286
|
+
return attachmentHandleForState(state);
|
|
2210
2287
|
}
|
|
2211
2288
|
}
|
|
2212
2289
|
}
|