impel-cli 0.20.17 → 0.20.19
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 +19 -0
- package/package.json +1 -1
- package/scripts/analyze-native-codex.mjs +5 -0
- package/scripts/profile-native-codex.mjs +39 -10
- package/src/agents.js +5 -1
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## 0.20.19 — Recovered Codex stream accounting
|
|
4
|
+
|
|
5
|
+
- Counts a zero-exit, completed Codex turn with a final agent message and one
|
|
6
|
+
native-agent start as successful even when the host reports transient stream
|
|
7
|
+
retry events before recovering.
|
|
8
|
+
- Records recovered error-event counts separately in private attempt records
|
|
9
|
+
and aggregate analysis instead of silently discarding the successful sample.
|
|
10
|
+
- Prevents generated Codex specialists from using collaboration messages to
|
|
11
|
+
report their terminal result, and refreshes already-fresh managed profiles
|
|
12
|
+
onto the tightened adapter instructions.
|
|
13
|
+
|
|
14
|
+
## 0.20.18 — Recoverable Codex profiling cohorts
|
|
15
|
+
|
|
16
|
+
- Widens the bounded retry window for transient current-tenant reads while
|
|
17
|
+
still rejecting an exact tenant mismatch before a Codex attempt starts.
|
|
18
|
+
- Stops scheduling new work after an attempt-infrastructure failure, waits for
|
|
19
|
+
active workers to settle, and retains an explicitly incomplete private
|
|
20
|
+
aggregate so valid samples are not silently lost.
|
|
21
|
+
|
|
3
22
|
## 0.20.17 — Managed desktop task views
|
|
4
23
|
|
|
5
24
|
- Enables the reviewed MCP Apps renderer only in exact managed Codex Desktop
|
package/package.json
CHANGED
|
@@ -89,6 +89,7 @@ export function summarizeCodexEvents(events) {
|
|
|
89
89
|
let outputTokens = 0;
|
|
90
90
|
let turnCompleted = false;
|
|
91
91
|
let errorEvents = 0;
|
|
92
|
+
let agentMessages = 0;
|
|
92
93
|
let codeCells = 0;
|
|
93
94
|
let codeWaits = 0;
|
|
94
95
|
|
|
@@ -102,6 +103,7 @@ export function summarizeCodexEvents(events) {
|
|
|
102
103
|
|
|
103
104
|
const item = eventItem(event);
|
|
104
105
|
const itemType = safeEventType(item.type);
|
|
106
|
+
if (type === "item.completed" && itemType === "agent_message") agentMessages += 1;
|
|
105
107
|
const tool = eventTool(item);
|
|
106
108
|
increment(toolCounts, tool);
|
|
107
109
|
if (itemType && /(?:code_mode|command_execution|code_cell)/u.test(itemType)) codeCells += 1;
|
|
@@ -123,6 +125,7 @@ export function summarizeCodexEvents(events) {
|
|
|
123
125
|
threadCount: threads.size,
|
|
124
126
|
turnCompleted,
|
|
125
127
|
errorEvents,
|
|
128
|
+
agentMessages,
|
|
126
129
|
codeCells,
|
|
127
130
|
codeWaits,
|
|
128
131
|
inputTokens,
|
|
@@ -410,6 +413,8 @@ export function analyzeAggregates(aggregates) {
|
|
|
410
413
|
timeout: attempts.filter((attempt) => attempt.timedOut).length,
|
|
411
414
|
invalidJson: attempts.filter((attempt) => !attempt.jsonValid).length,
|
|
412
415
|
descendantsRemaining: attempts.filter((attempt) => attempt.descendantsRemaining === true).length,
|
|
416
|
+
attemptsWithRecoveredErrors: successful.filter((attempt) => (attempt.errorEvents || 0) > 0).length,
|
|
417
|
+
recoveredErrorEvents: successful.reduce((total, attempt) => total + (attempt.errorEvents || 0), 0),
|
|
413
418
|
},
|
|
414
419
|
};
|
|
415
420
|
report.gates = report.mode === "direct" ? directGate(report) : compatibleGate(report);
|
|
@@ -14,6 +14,7 @@ const PROFILE_SCHEMA = "impel.native-codex-profile.v1";
|
|
|
14
14
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
15
15
|
const DEFAULT_GRACE_MS = 5_000;
|
|
16
16
|
const MAX_CAPTURE_BYTES = 256 * 1024 * 1024;
|
|
17
|
+
const TENANT_PREFLIGHT_RETRY_DELAYS_MS = [250, 750, 1_500];
|
|
17
18
|
|
|
18
19
|
function privateDirectory(directory) {
|
|
19
20
|
if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink()) {
|
|
@@ -211,16 +212,17 @@ function runCaptured(command, args, { environment = process.env, timeoutMs = 60_
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
async function currentTenant(impelBinary, environment) {
|
|
214
|
-
for (let attempt = 0; attempt
|
|
215
|
+
for (let attempt = 0; attempt <= TENANT_PREFLIGHT_RETRY_DELAYS_MS.length; attempt += 1) {
|
|
215
216
|
const invocation = impelInvocation(impelBinary, ["tenant", "current"], environment);
|
|
216
217
|
const result = await runCaptured(invocation.command, invocation.args, { environment });
|
|
217
218
|
const lines = result.stdout.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
|
|
218
219
|
if (result.code === 0 && lines.length === 1 && /^[A-Za-z0-9_.:-]{1,160}$/u.test(lines[0])) {
|
|
219
220
|
return lines[0];
|
|
220
221
|
}
|
|
221
|
-
|
|
222
|
+
const retryDelay = TENANT_PREFLIGHT_RETRY_DELAYS_MS[attempt];
|
|
223
|
+
if (retryDelay !== undefined) await delay(retryDelay);
|
|
222
224
|
}
|
|
223
|
-
throw new Error("could not establish one exact current Impel tenant after
|
|
225
|
+
throw new Error("could not establish one exact current Impel tenant after bounded attempts");
|
|
224
226
|
}
|
|
225
227
|
|
|
226
228
|
export async function assertExpectedTenant({ expectedTenant, impelBinary = "impel", environment = process.env }) {
|
|
@@ -372,7 +374,7 @@ function parseArguments(argv) {
|
|
|
372
374
|
return { ...options, help: false };
|
|
373
375
|
}
|
|
374
376
|
|
|
375
|
-
function safeAttemptRecord({ options, index, processResult, summary, jsonValid, parseError }) {
|
|
377
|
+
export function safeAttemptRecord({ options, index, processResult, summary, jsonValid, parseError }) {
|
|
376
378
|
const codex = summary?.codex || {};
|
|
377
379
|
const telemetry = summary?.telemetry || {};
|
|
378
380
|
const durationMs = processResult.endedAtMs - processResult.startedAtMs;
|
|
@@ -387,7 +389,7 @@ function safeAttemptRecord({ options, index, processResult, summary, jsonValid,
|
|
|
387
389
|
&& processResult.descendantsRemaining !== true
|
|
388
390
|
&& jsonValid
|
|
389
391
|
&& codex.turnCompleted === true
|
|
390
|
-
&& (codex.
|
|
392
|
+
&& (codex.agentMessages || 0) >= 1
|
|
391
393
|
&& (telemetry.localFailures || 0) === 0
|
|
392
394
|
&& startCalls === 1;
|
|
393
395
|
return {
|
|
@@ -410,6 +412,8 @@ function safeAttemptRecord({ options, index, processResult, summary, jsonValid,
|
|
|
410
412
|
parseFailureClass: parseError ? "invalid-jsonl" : null,
|
|
411
413
|
success,
|
|
412
414
|
eventCount: codex.eventCount || 0,
|
|
415
|
+
agentMessages: codex.agentMessages || 0,
|
|
416
|
+
errorEvents: codex.errorEvents || 0,
|
|
413
417
|
threadCount: codex.threadCount || 0,
|
|
414
418
|
toolCounts: codex.toolCounts || {},
|
|
415
419
|
parentToolCounts: codex.parentToolCounts || {},
|
|
@@ -495,19 +499,38 @@ async function profileAttempt(options, sessionDir, index) {
|
|
|
495
499
|
return record;
|
|
496
500
|
}
|
|
497
501
|
|
|
498
|
-
|
|
502
|
+
function profilerFailureClass(error) {
|
|
503
|
+
if (/could not establish one exact current Impel tenant/u.test(error?.message || "")) {
|
|
504
|
+
return "tenant-preflight-unavailable";
|
|
505
|
+
}
|
|
506
|
+
if (/selected tenant .* does not match expected tenant/u.test(error?.message || "")) {
|
|
507
|
+
return "tenant-mismatch";
|
|
508
|
+
}
|
|
509
|
+
return "attempt-infrastructure";
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
export async function runWorkers(options, sessionDir, profileAttemptImpl = profileAttempt) {
|
|
499
513
|
const results = new Array(options.attempts);
|
|
500
514
|
let next = 0;
|
|
515
|
+
let abort = null;
|
|
501
516
|
const worker = async () => {
|
|
502
517
|
for (;;) {
|
|
518
|
+
if (abort) return;
|
|
503
519
|
const index = next;
|
|
504
520
|
next += 1;
|
|
505
521
|
if (index >= options.attempts) return;
|
|
506
|
-
|
|
522
|
+
try {
|
|
523
|
+
results[index] = await profileAttemptImpl(options, sessionDir, index);
|
|
524
|
+
} catch (error) {
|
|
525
|
+
abort ||= {
|
|
526
|
+
failureClass: profilerFailureClass(error),
|
|
527
|
+
attemptId: `${options.cohort}-${String(index + 1 + options.attemptOffset).padStart(3, "0")}`,
|
|
528
|
+
};
|
|
529
|
+
}
|
|
507
530
|
}
|
|
508
531
|
};
|
|
509
532
|
await Promise.all(Array.from({ length: options.concurrency }, () => worker()));
|
|
510
|
-
return results;
|
|
533
|
+
return { attempts: results.filter(Boolean), abort };
|
|
511
534
|
}
|
|
512
535
|
|
|
513
536
|
async function restoreTenant(originalTenant, options) {
|
|
@@ -553,7 +576,7 @@ async function main(argv) {
|
|
|
553
576
|
privateDirectory(sessionDir);
|
|
554
577
|
let restored = false;
|
|
555
578
|
try {
|
|
556
|
-
const
|
|
579
|
+
const result = await runWorkers(options, sessionDir);
|
|
557
580
|
const aggregate = {
|
|
558
581
|
schema: PROFILE_SCHEMA,
|
|
559
582
|
sessionId,
|
|
@@ -564,12 +587,18 @@ async function main(argv) {
|
|
|
564
587
|
cohort: options.cohort,
|
|
565
588
|
cliVersion: options.hostBuild,
|
|
566
589
|
promptSha256: options.promptSha256,
|
|
567
|
-
attempts,
|
|
590
|
+
complete: result.abort === null && result.attempts.length === options.attempts,
|
|
591
|
+
expectedAttempts: options.attempts,
|
|
592
|
+
abort: result.abort,
|
|
593
|
+
attempts: result.attempts,
|
|
568
594
|
};
|
|
569
595
|
const aggregatePath = path.join(sessionDir, "aggregate.json");
|
|
570
596
|
privateWrite(aggregatePath, `${JSON.stringify(aggregate, null, 2)}\n`);
|
|
571
597
|
process.stderr.write(`Private Codex profile written under ${sessionDir}\n`);
|
|
572
598
|
process.stdout.write(`${JSON.stringify(aggregate)}\n`);
|
|
599
|
+
if (!aggregate.complete) {
|
|
600
|
+
throw new Error(`cohort aborted (${aggregate.abort?.failureClass || "incomplete"}); partial aggregate retained`);
|
|
601
|
+
}
|
|
573
602
|
} finally {
|
|
574
603
|
restored = await restoreTenant(originalTenant, options);
|
|
575
604
|
if (!restored) process.stderr.write("profile-native-codex: could not verify restoration of the original tenant\n");
|
package/src/agents.js
CHANGED
|
@@ -52,7 +52,7 @@ export const NATIVE_AGENT_RUN_TOOL = "run_native_agent";
|
|
|
52
52
|
export const NATIVE_AGENT_RESUME_TOOL = "resume_native_agent_run";
|
|
53
53
|
export const NATIVE_AGENT_RECOVER_TOOL = "recover_native_agent_runs";
|
|
54
54
|
export const MANAGED_AGENT_MCP_SERVER = "impel_agent";
|
|
55
|
-
export const MANAGED_AGENT_MANIFEST_VERSION =
|
|
55
|
+
export const MANAGED_AGENT_MANIFEST_VERSION = 11;
|
|
56
56
|
|
|
57
57
|
const NATIVE_AGENT_TOOL_NAMES = [
|
|
58
58
|
NATIVE_AGENT_RUN_TOOL,
|
|
@@ -650,6 +650,7 @@ function codexAdapterInstructions(tenantId, agent) {
|
|
|
650
650
|
const callerGuidance = usesVerbatimRelay(agent)
|
|
651
651
|
? adapterCallerSpawnGuidance("Codex")
|
|
652
652
|
: null;
|
|
653
|
+
const collaborationConstraint = `Never call spawn_agent, wait_agent, send_message, followup_task, interrupt_agent, or any other collaboration tool. Communicate with the caller only by returning one final response after the native-agent transport reaches terminal.`;
|
|
653
654
|
if (usesDirectAnswer(agent)) {
|
|
654
655
|
const completionGuidance = usesVerbatimRelay(agent)
|
|
655
656
|
? adapterAnswerVerbatimCompletionGuidance()
|
|
@@ -657,6 +658,7 @@ function codexAdapterInstructions(tenantId, agent) {
|
|
|
657
658
|
return [
|
|
658
659
|
`You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
|
|
659
660
|
...(callerGuidance ? [callerGuidance] : []),
|
|
661
|
+
collaborationConstraint,
|
|
660
662
|
`Do not perform the assigned task yourself, do not delegate to any other agent, and do not independently synthesize the result.`,
|
|
661
663
|
`Confirm that the assigned request fits the cataloged capabilities ${JSON.stringify(agent.capabilities)} and none of the exclusions ${JSON.stringify(agent.exclusions)} before answering.${contextRequirement}`,
|
|
662
664
|
sideEffectInstruction,
|
|
@@ -671,6 +673,7 @@ function codexAdapterInstructions(tenantId, agent) {
|
|
|
671
673
|
return [
|
|
672
674
|
`You are a thin transport adapter for the exact Impel native agent ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
|
|
673
675
|
...(callerGuidance ? [callerGuidance] : []),
|
|
676
|
+
collaborationConstraint,
|
|
674
677
|
`Do not perform the assigned task yourself, do not delegate to any other agent, and do not independently synthesize or rewrite the result.`,
|
|
675
678
|
`Confirm that the assigned request fits the cataloged capabilities ${JSON.stringify(agent.capabilities)} and none of the exclusions ${JSON.stringify(agent.exclusions)} before starting.${contextRequirement}`,
|
|
676
679
|
sideEffectInstruction,
|
|
@@ -683,6 +686,7 @@ function codexAdapterInstructions(tenantId, agent) {
|
|
|
683
686
|
function retiredAdapterInstructions(tenantId, agent) {
|
|
684
687
|
return [
|
|
685
688
|
`You are a recovery-only transport adapter for retired Impel native-agent binding ${JSON.stringify(agent.agentId)} in tenant ${JSON.stringify(tenantId)}.`,
|
|
689
|
+
`Never call spawn_agent, wait_agent, send_message, followup_task, interrupt_agent, or any other collaboration tool. Communicate with the caller only by returning one final response after the native-agent transport reaches terminal.`,
|
|
686
690
|
`You cannot start new work. Never call run_native_agent and never perform or recreate the assigned task yourself.`,
|
|
687
691
|
`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 until it returns a terminal result. If multiple handles cannot be safely associated with the request, report them instead of choosing.`,
|
|
688
692
|
`Return successful finalText byte-for-byte. For failure, return the preserved runId, output, and error without inventing a replacement result.`,
|