mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-12 06:06:37 +03:00
All three new findings are the same root cause, and it is my incomplete fix:
operationFailure only covered results built from a *thrown* error. Two paths it
missed:
- Two catches wrote the failure result as a multi-line literal, so the
single-line replace skipped them. The Vue accept catch was still bare, exactly
as reported; the Svelte one too, though its failures happened to be caught by
completion.mjs's Svelte-only special case.
- The accept implementations also *return* `{handled: false, error}` for their own
checks (variant missing, template empty, original text ambiguous). Those never
throw, so no catch ran and no `mode` was set.
Both layers now agree, because each is reachable on its own:
- live-accept marks any unhandled preview-path result via markPreviewFailure,
keyed on `previewMode` — a clean discriminator, since only the preview branches
set it and a plain wrapper never does. This is what the agent reads:
reference/live.md routes on `mode`, so without it the agent was told "read
file, find markers, edit" for a preview that has no markers in source.
- completion.mjs replaces its arbitrary svelte-component special case with the
set of preview modes whose variants live outside the user's source. That case
existed for precisely this reason; Vue and source-artifact were simply never
added, so the identical failure on those paths acknowledged as success.
The plain wrapper keeps its manual handoff, which is the one shape with editable
markers in source. Both deliberate handoffs (mode: 'fallback' and markers not
found) still classify as agent_done, now pinned by a test so the generalization
cannot swallow them.
Prepared with AI assistance under maintainer direction.
Co-Authored-By: Claude <noreply@anthropic.com>
85 lines
3.8 KiB
JavaScript
85 lines
3.8 KiB
JavaScript
import { describe, it } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
completionAckForAcceptResult,
|
|
completionTypeForAcceptResult,
|
|
} from '../skill/scripts/live/completion.mjs';
|
|
|
|
describe('live completion type classification', () => {
|
|
it('treats generated-file fallback accept as normal agent handoff, not error', () => {
|
|
assert.equal(
|
|
completionTypeForAcceptResult('accept', { handled: false, mode: 'fallback' }),
|
|
'agent_done',
|
|
'event=live_poll.fallback_completion actor=agent operation=accept_generated_file risk=fallback_handoff_recorded_as_agent_error expected=agent_done actual=error',
|
|
);
|
|
});
|
|
|
|
it('treats unhandled non-error accept as normal manual agent handoff', () => {
|
|
assert.equal(
|
|
completionTypeForAcceptResult('accept', { handled: false, error: 'Session markers not found' }),
|
|
'agent_done',
|
|
'event=live_poll.manual_accept_completion actor=agent operation=accept_manual_cleanup risk=manual_handoff_recorded_as_agent_error expected=agent_done actual=error',
|
|
);
|
|
});
|
|
|
|
it('keeps carbonize-required accepts recoverable until cleanup is completed', () => {
|
|
assert.equal(
|
|
completionTypeForAcceptResult('accept', { handled: true, carbonize: true }),
|
|
'agent_done',
|
|
'event=live_poll.carbonize_completion actor=agent operation=accept_with_carbonize risk=carbonize_session_marked_completed_before_cleanup expected=agent_done actual=complete',
|
|
);
|
|
});
|
|
|
|
it('marks carbonize acknowledgements as non-final and requiring explicit completion', () => {
|
|
assert.deepEqual(
|
|
completionAckForAcceptResult('carbonize-1', 'agent_done', { handled: true, carbonize: true }),
|
|
{
|
|
ok: true,
|
|
type: 'agent_done',
|
|
final: false,
|
|
requiresComplete: true,
|
|
nextCommand: 'live-complete.mjs --id carbonize-1',
|
|
message: 'Carbonize cleanup must be verified, then the session must be completed explicitly before polling again.',
|
|
},
|
|
'event=live_poll.carbonize_ack actor=agent operation=accept_with_carbonize risk=active_session_never_completed expected=explicit_complete_required actual=missing_requires_complete',
|
|
);
|
|
});
|
|
|
|
it('keeps normal handled accepts terminal', () => {
|
|
assert.deepEqual(
|
|
completionAckForAcceptResult('done-1', 'complete', { handled: true, carbonize: false }),
|
|
{ ok: true, type: 'complete' },
|
|
);
|
|
});
|
|
|
|
// Previews whose variants live outside the user's source leave nothing in the
|
|
// file to hand-edit, so a failed accept there is a failure, not live.md's
|
|
// "read file, find markers, edit" handoff. Only svelte-component was special
|
|
// cased, so the identical failure on a Vue or isolated-artifact preview was
|
|
// acknowledged as a success and Live continued past it.
|
|
for (const previewMode of ['svelte-component', 'vue-component', 'source-artifact']) {
|
|
it(`treats a failed ${previewMode} accept as an error, not a manual handoff`, () => {
|
|
assert.equal(
|
|
completionTypeForAcceptResult('accept', { handled: false, error: 'source_locked', previewMode }),
|
|
'error',
|
|
);
|
|
});
|
|
}
|
|
|
|
it('still treats a failed plain-wrapper accept as a manual handoff', () => {
|
|
// The one shape with editable markers in source. This must not regress into
|
|
// an error, or every hand-editable session starts failing the poll loop.
|
|
assert.equal(
|
|
completionTypeForAcceptResult('accept', { handled: false, error: 'Markers not found' }),
|
|
'agent_done',
|
|
);
|
|
});
|
|
|
|
it('classifies handled accept/discard and real failures explicitly', () => {
|
|
assert.equal(completionTypeForAcceptResult('accept', { handled: true }), 'complete');
|
|
assert.equal(completionTypeForAcceptResult('discard', { handled: true }), 'discarded');
|
|
assert.equal(completionTypeForAcceptResult('accept', { handled: false, mode: 'error', error: 'boom' }), 'error');
|
|
});
|
|
});
|