@principles/core 1.278.5 → 1.278.7
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/dist/runtime-v2/__tests__/recovery-sweep-service.test.js +130 -0
- package/dist/runtime-v2/__tests__/recovery-sweep-service.test.js.map +1 -1
- package/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js +157 -0
- package/dist/runtime-v2/adapter/__tests__/pi-ai-runtime-adapter.test.js.map +1 -1
- package/dist/runtime-v2/adapter/__tests__/structured-output-repair.test.js +9 -0
- package/dist/runtime-v2/adapter/__tests__/structured-output-repair.test.js.map +1 -1
- package/dist/runtime-v2/adapter/output-repair-contract.d.ts +10 -0
- package/dist/runtime-v2/adapter/output-repair-contract.d.ts.map +1 -1
- package/dist/runtime-v2/adapter/output-repair-contract.js.map +1 -1
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.d.ts.map +1 -1
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js +53 -1
- package/dist/runtime-v2/adapter/pi-ai-runtime-adapter.js.map +1 -1
- package/dist/runtime-v2/adapter/structured-output-repair.d.ts +9 -1
- package/dist/runtime-v2/adapter/structured-output-repair.d.ts.map +1 -1
- package/dist/runtime-v2/adapter/structured-output-repair.js +8 -0
- package/dist/runtime-v2/adapter/structured-output-repair.js.map +1 -1
- package/dist/runtime-v2/recovery-sweep-service.d.ts +9 -0
- package/dist/runtime-v2/recovery-sweep-service.d.ts.map +1 -1
- package/dist/runtime-v2/recovery-sweep-service.js +7 -2
- package/dist/runtime-v2/recovery-sweep-service.js.map +1 -1
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-failure-details.test.js +36 -0
- package/dist/runtime-v2/runner/__tests__/base-peer-runner-failure-details.test.js.map +1 -1
- package/package.json +1 -1
|
@@ -5,6 +5,9 @@ const mockRecoverTask = vi.fn();
|
|
|
5
5
|
const mockStateManagerClose = vi.fn();
|
|
6
6
|
const mockInitialize = vi.fn();
|
|
7
7
|
const mockAssertInitialized = vi.fn();
|
|
8
|
+
const mockListTasks = vi.fn();
|
|
9
|
+
const mockGetTask = vi.fn();
|
|
10
|
+
const mockUpdateTask = vi.fn();
|
|
8
11
|
vi.mock('../store/runtime-state-manager.js', () => ({
|
|
9
12
|
RuntimeStateManager: vi.fn().mockImplementation(function () {
|
|
10
13
|
this.initialize = mockInitialize;
|
|
@@ -13,8 +16,23 @@ vi.mock('../store/runtime-state-manager.js', () => ({
|
|
|
13
16
|
this.recoverTask = mockRecoverTask;
|
|
14
17
|
this.assertInitialized = mockAssertInitialized;
|
|
15
18
|
this.isInitialized = true;
|
|
19
|
+
this.listTasks = mockListTasks;
|
|
20
|
+
this.getTask = mockGetTask;
|
|
21
|
+
this.updateTask = mockUpdateTask;
|
|
16
22
|
}),
|
|
17
23
|
}));
|
|
24
|
+
function makeTask(overrides = {}) {
|
|
25
|
+
return {
|
|
26
|
+
taskId: 'task-x',
|
|
27
|
+
taskKind: 'dreamer',
|
|
28
|
+
status: 'failed',
|
|
29
|
+
createdAt: '2026-09-08T00:00:00.000Z',
|
|
30
|
+
updatedAt: '2026-09-08T00:00:00.000Z',
|
|
31
|
+
attemptCount: 1,
|
|
32
|
+
maxAttempts: 3,
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
18
36
|
describe('createRecoverySweepService', () => {
|
|
19
37
|
beforeEach(() => {
|
|
20
38
|
vi.clearAllMocks();
|
|
@@ -80,4 +98,116 @@ describe('createRecoverySweepService', () => {
|
|
|
80
98
|
.rejects.toThrow('DB init failed');
|
|
81
99
|
});
|
|
82
100
|
});
|
|
101
|
+
describe('detectFailedTasks — recovery discovery (PRI-674)', () => {
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
vi.clearAllMocks();
|
|
104
|
+
vi.restoreAllMocks();
|
|
105
|
+
mockInitialize.mockResolvedValue(undefined);
|
|
106
|
+
mockStateManagerClose.mockResolvedValue(undefined);
|
|
107
|
+
mockAssertInitialized.mockReturnValue(undefined);
|
|
108
|
+
mockListTasks.mockResolvedValue([]);
|
|
109
|
+
});
|
|
110
|
+
it('returns failed diagnostician parent and diag_* stage tasks (GAP-1 regression)', async () => {
|
|
111
|
+
mockListTasks.mockResolvedValue([
|
|
112
|
+
makeTask({ taskId: 'diag-parent', taskKind: 'diagnostician', inputRef: 'pain-001' }),
|
|
113
|
+
makeTask({ taskId: 'diag-a', taskKind: 'diag_rootcause', inputRef: 'diag-parent' }),
|
|
114
|
+
makeTask({ taskId: 'diag-b', taskKind: 'diag_distiller', inputRef: 'diag-parent' }),
|
|
115
|
+
makeTask({ taskId: 'diag-c', taskKind: 'diag_router', inputRef: 'diag-parent' }),
|
|
116
|
+
]);
|
|
117
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
118
|
+
const results = await handle.service.detectFailedTasks();
|
|
119
|
+
expect(results.map((r) => r.taskId).sort())
|
|
120
|
+
.toEqual(['diag-a', 'diag-b', 'diag-c', 'diag-parent']);
|
|
121
|
+
// inputRef is the persisted stage→parent linkage consumed by CLI
|
|
122
|
+
// execution guidance (PRI-674 review P2).
|
|
123
|
+
expect(results.find((r) => r.taskId === 'diag-a')?.inputRef).toBe('diag-parent');
|
|
124
|
+
expect(results.find((r) => r.taskId === 'diag-parent')?.inputRef).toBe('pain-001');
|
|
125
|
+
});
|
|
126
|
+
it('returns inputRef null when the task record has none', async () => {
|
|
127
|
+
mockListTasks.mockResolvedValue([
|
|
128
|
+
makeTask({ taskId: 'no-ref', taskKind: 'diag_router', inputRef: undefined }),
|
|
129
|
+
]);
|
|
130
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
131
|
+
const results = await handle.service.detectFailedTasks();
|
|
132
|
+
expect(results).toHaveLength(1);
|
|
133
|
+
expect(results[0]?.inputRef).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it('still returns peer runner kinds with isExhausted computed', async () => {
|
|
136
|
+
mockListTasks.mockResolvedValue([
|
|
137
|
+
makeTask({ taskId: 'peer-dreamer', taskKind: 'dreamer', attemptCount: 1, maxAttempts: 3 }),
|
|
138
|
+
makeTask({ taskId: 'peer-rollout', taskKind: 'rollout_reviewer', attemptCount: 3, maxAttempts: 3 }),
|
|
139
|
+
]);
|
|
140
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
141
|
+
const results = await handle.service.detectFailedTasks();
|
|
142
|
+
expect(results.map((r) => r.taskId).sort()).toEqual(['peer-dreamer', 'peer-rollout']);
|
|
143
|
+
expect(results.find((r) => r.taskId === 'peer-dreamer')?.isExhausted).toBe(false);
|
|
144
|
+
expect(results.find((r) => r.taskId === 'peer-rollout')?.isExhausted).toBe(true);
|
|
145
|
+
});
|
|
146
|
+
it('excludes failed tasks of unrelated kinds', async () => {
|
|
147
|
+
mockListTasks.mockResolvedValue([
|
|
148
|
+
makeTask({ taskId: 'other', taskKind: 'principle_candidate_intake' }),
|
|
149
|
+
]);
|
|
150
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
151
|
+
const results = await handle.service.detectFailedTasks();
|
|
152
|
+
expect(results).toEqual([]);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
describe('recoverFailedTask — reset semantics unchanged (PRI-674)', () => {
|
|
156
|
+
beforeEach(() => {
|
|
157
|
+
vi.clearAllMocks();
|
|
158
|
+
vi.restoreAllMocks();
|
|
159
|
+
mockInitialize.mockResolvedValue(undefined);
|
|
160
|
+
mockStateManagerClose.mockResolvedValue(undefined);
|
|
161
|
+
mockAssertInitialized.mockReturnValue(undefined);
|
|
162
|
+
});
|
|
163
|
+
it('resets a failed diagnostician task to pending with lease/attempts/result cleared', async () => {
|
|
164
|
+
mockGetTask.mockResolvedValue(makeTask({
|
|
165
|
+
taskId: 'diag-parent',
|
|
166
|
+
taskKind: 'diagnostician',
|
|
167
|
+
attemptCount: 1,
|
|
168
|
+
maxAttempts: 3,
|
|
169
|
+
lastError: 'max_attempts_exceeded',
|
|
170
|
+
leaseOwner: 'runner-1',
|
|
171
|
+
leaseExpiresAt: '2026-09-08T01:00:00.000Z',
|
|
172
|
+
resultRef: 'artifact://partial',
|
|
173
|
+
}));
|
|
174
|
+
mockUpdateTask.mockImplementation(async (taskId, patch) => makeTask({ taskId, taskKind: 'diagnostician', ...patch }));
|
|
175
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
176
|
+
const result = await handle.service.recoverFailedTask('diag-parent');
|
|
177
|
+
expect(mockUpdateTask).toHaveBeenCalledWith('diag-parent', {
|
|
178
|
+
status: 'pending',
|
|
179
|
+
attemptCount: 0,
|
|
180
|
+
maxAttempts: 3,
|
|
181
|
+
lastError: null,
|
|
182
|
+
leaseOwner: null,
|
|
183
|
+
leaseExpiresAt: null,
|
|
184
|
+
resultRef: null,
|
|
185
|
+
});
|
|
186
|
+
expect(result).toMatchObject({
|
|
187
|
+
taskId: 'diag-parent',
|
|
188
|
+
previousStatus: 'failed',
|
|
189
|
+
newStatus: 'pending',
|
|
190
|
+
forceApplied: false,
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
it('refuses an exhausted task without force (retry guard unchanged)', async () => {
|
|
194
|
+
mockGetTask.mockResolvedValue(makeTask({
|
|
195
|
+
taskId: 'diag-exhausted',
|
|
196
|
+
taskKind: 'diagnostician',
|
|
197
|
+
attemptCount: 3,
|
|
198
|
+
maxAttempts: 3,
|
|
199
|
+
}));
|
|
200
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
201
|
+
await expect(handle.service.recoverFailedTask('diag-exhausted'))
|
|
202
|
+
.rejects.toThrow(/exhausted max attempts/);
|
|
203
|
+
expect(mockUpdateTask).not.toHaveBeenCalled();
|
|
204
|
+
});
|
|
205
|
+
it('returns null for a task that is not failed', async () => {
|
|
206
|
+
mockGetTask.mockResolvedValue(makeTask({ taskId: 'diag-ok', status: 'succeeded' }));
|
|
207
|
+
const handle = await createRecoverySweepService({ workspaceDir: '/tmp/test-ws' });
|
|
208
|
+
const result = await handle.service.recoverFailedTask('diag-ok');
|
|
209
|
+
expect(result).toBeNull();
|
|
210
|
+
expect(mockUpdateTask).not.toHaveBeenCalled();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
83
213
|
//# sourceMappingURL=recovery-sweep-service.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recovery-sweep-service.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/recovery-sweep-service.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9D,OAAO,EAAE,0BAA0B,EAAE,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"recovery-sweep-service.test.js","sourceRoot":"","sources":["../../../src/runtime-v2/__tests__/recovery-sweep-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1937,5 +1937,162 @@ describe('PiAiRuntimeAdapter', () => {
|
|
|
1937
1937
|
expect(output?.payload).toMatchObject({ diagnosisId: 'diag-test-1' });
|
|
1938
1938
|
});
|
|
1939
1939
|
});
|
|
1940
|
+
// ── PRI-707: finish metadata preservation + truncation classification ──
|
|
1941
|
+
//
|
|
1942
|
+
// Before PRI-707 the provider's stopReason/usage were dropped whenever any
|
|
1943
|
+
// text was extractable, so a token-limit cut was indistinguishable from a
|
|
1944
|
+
// true format error ("No valid JSON found in LLM response"). These tests
|
|
1945
|
+
// pin the preserved evidence surfaces: error details (→ durable
|
|
1946
|
+
// diagnosticJson via the runner's classifyError path), telemetry payloads,
|
|
1947
|
+
// and the repair-prompt truncation notice.
|
|
1948
|
+
describe('PRI-707: finish metadata preservation and truncation classification', () => {
|
|
1949
|
+
function makeSchemaInput(overrides = {}) {
|
|
1950
|
+
return {
|
|
1951
|
+
agentSpec: { agentId: 'diagnostician', schemaVersion: 'v1' },
|
|
1952
|
+
inputPayload: 'Diagnose this pain signal',
|
|
1953
|
+
contextItems: [],
|
|
1954
|
+
timeoutMs: 60_000,
|
|
1955
|
+
outputSchemaRef: 'diagnostician-output-v1',
|
|
1956
|
+
...overrides,
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
/** Type-guard the repair prompt out of a completeSimple mock call (rc-2). */
|
|
1960
|
+
function repairPromptFromCall(call) {
|
|
1961
|
+
if (!Array.isArray(call) || call.length < 2)
|
|
1962
|
+
return undefined;
|
|
1963
|
+
const [context] = call.slice(1, 2);
|
|
1964
|
+
if (typeof context !== 'object' || context === null || !Object.hasOwn(context, 'messages'))
|
|
1965
|
+
return undefined;
|
|
1966
|
+
const { messages } = context;
|
|
1967
|
+
if (!Array.isArray(messages) || messages.length === 0)
|
|
1968
|
+
return undefined;
|
|
1969
|
+
const [first] = messages;
|
|
1970
|
+
if (typeof first !== 'object' || first === null || !Object.hasOwn(first, 'content'))
|
|
1971
|
+
return undefined;
|
|
1972
|
+
const { content } = first;
|
|
1973
|
+
return typeof content === 'string' ? content : undefined;
|
|
1974
|
+
}
|
|
1975
|
+
function usageWith(output) {
|
|
1976
|
+
return { input: 120, output, cacheRead: 0, cacheWrite: 0, totalTokens: 120 + output, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } };
|
|
1977
|
+
}
|
|
1978
|
+
it('T3+T4: finish_reason=length + usage → extraction failure carries truncated evidence (details + telemetry)', async () => {
|
|
1979
|
+
mockComplete.mockResolvedValueOnce(makeAssistantMessage('{"diagnosisId":"diag-trunc', {
|
|
1980
|
+
stopReason: 'length',
|
|
1981
|
+
usage: usageWith(64),
|
|
1982
|
+
}));
|
|
1983
|
+
const adapter = makeAdapter(); // free_form_only → Path 3 directly
|
|
1984
|
+
let caught;
|
|
1985
|
+
try {
|
|
1986
|
+
await adapter.startRun(makeSchemaInput());
|
|
1987
|
+
}
|
|
1988
|
+
catch (err) {
|
|
1989
|
+
caught = err instanceof PDRuntimeError ? err : undefined;
|
|
1990
|
+
}
|
|
1991
|
+
expect(caught?.category).toBe('output_invalid');
|
|
1992
|
+
expect(caught?.message).toContain('finish_reason=length');
|
|
1993
|
+
// The runner persists err.details into diagnosticJson.output_failure_details
|
|
1994
|
+
// (PRI-559 P0-2) — so these fields become durable failure evidence.
|
|
1995
|
+
expect(caught?.details).toMatchObject({
|
|
1996
|
+
truncated: true,
|
|
1997
|
+
stopReason: 'length',
|
|
1998
|
+
outputTokens: 64,
|
|
1999
|
+
});
|
|
2000
|
+
expect(typeof caught?.details?.nextAction).toBe('string');
|
|
2001
|
+
const extractionEvent = findTelemetryEvent('output_extraction_failed');
|
|
2002
|
+
expect(extractionEvent?.payload).toMatchObject({
|
|
2003
|
+
stopReason: 'length',
|
|
2004
|
+
outputTokens: 64,
|
|
2005
|
+
truncated: true,
|
|
2006
|
+
});
|
|
2007
|
+
});
|
|
2008
|
+
it('T5: finish_reason=stop + no extractable JSON → conservative legacy classification, no truncation claim', async () => {
|
|
2009
|
+
mockComplete.mockResolvedValueOnce(makeAssistantMessage('I cannot answer that.'));
|
|
2010
|
+
const adapter = makeAdapter();
|
|
2011
|
+
let caught;
|
|
2012
|
+
try {
|
|
2013
|
+
await adapter.startRun(makeSchemaInput());
|
|
2014
|
+
}
|
|
2015
|
+
catch (err) {
|
|
2016
|
+
caught = err instanceof PDRuntimeError ? err : undefined;
|
|
2017
|
+
}
|
|
2018
|
+
expect(caught?.category).toBe('output_invalid');
|
|
2019
|
+
expect(caught?.message).toBe('[output_invalid] No valid JSON found in LLM response');
|
|
2020
|
+
expect(caught?.details).toBeUndefined();
|
|
2021
|
+
const extractionEvent = findTelemetryEvent('output_extraction_failed');
|
|
2022
|
+
const payload = extractionEvent?.payload;
|
|
2023
|
+
expect(payload?.truncated).toBeUndefined();
|
|
2024
|
+
});
|
|
2025
|
+
it('T2: valid JSON + schema violation with finish_reason=stop → schema-invalid, never claimed as truncation', async () => {
|
|
2026
|
+
mockComplete.mockResolvedValueOnce(makeAssistantMessage('{"unexpected":true}'));
|
|
2027
|
+
// Repair attempts fail (unparseable) → exhausted.
|
|
2028
|
+
mockComplete.mockResolvedValue(makeAssistantMessage('still not json'));
|
|
2029
|
+
const adapter = makeAdapter();
|
|
2030
|
+
let caught;
|
|
2031
|
+
try {
|
|
2032
|
+
await adapter.startRun(makeSchemaInput());
|
|
2033
|
+
}
|
|
2034
|
+
catch (err) {
|
|
2035
|
+
caught = err instanceof PDRuntimeError ? err : undefined;
|
|
2036
|
+
}
|
|
2037
|
+
expect(caught?.category).toBe('output_invalid');
|
|
2038
|
+
expect(caught?.message).toContain('does not match');
|
|
2039
|
+
const evidencePack = caught?.details?.evidencePack;
|
|
2040
|
+
expect(evidencePack).toBeDefined();
|
|
2041
|
+
// The PRI-621 RC3 heuristic fires (object matches no required key)…
|
|
2042
|
+
expect(evidencePack?.truncationSuspected).toBe(true);
|
|
2043
|
+
// …but the definitive finish-metadata signal must NOT claim truncation.
|
|
2044
|
+
expect(evidencePack?.truncated).toBeUndefined();
|
|
2045
|
+
expect(evidencePack?.stopReason).toBe('stop');
|
|
2046
|
+
});
|
|
2047
|
+
it('T8: finish_reason=length + schema violation → repair prompt carries truncation notice and evidencePack marks truncated', async () => {
|
|
2048
|
+
mockComplete.mockResolvedValueOnce(makeAssistantMessage('{"unexpected":true}', { stopReason: 'length' }));
|
|
2049
|
+
mockComplete.mockResolvedValue(makeAssistantMessage('still not json'));
|
|
2050
|
+
const adapter = makeAdapter();
|
|
2051
|
+
let caught;
|
|
2052
|
+
try {
|
|
2053
|
+
await adapter.startRun(makeSchemaInput());
|
|
2054
|
+
}
|
|
2055
|
+
catch (err) {
|
|
2056
|
+
caught = err instanceof PDRuntimeError ? err : undefined;
|
|
2057
|
+
}
|
|
2058
|
+
const evidencePack = caught?.details?.evidencePack;
|
|
2059
|
+
expect(evidencePack?.truncated).toBe(true);
|
|
2060
|
+
expect(evidencePack?.stopReason).toBe('length');
|
|
2061
|
+
// Terminal telemetry must carry the same truncation evidence so
|
|
2062
|
+
// monitoring can distinguish a token-limit cut from an ordinary
|
|
2063
|
+
// schema failure (PR #1574 review finding). Default helper usage
|
|
2064
|
+
// reports output: 5.
|
|
2065
|
+
const exhaustedEvent = findTelemetryEvent('output_repair_exhausted');
|
|
2066
|
+
expect(exhaustedEvent?.payload).toMatchObject({
|
|
2067
|
+
stopReason: 'length',
|
|
2068
|
+
truncated: true,
|
|
2069
|
+
outputTokens: 5,
|
|
2070
|
+
});
|
|
2071
|
+
// The FIRST completeSimple call is the original generation; repair
|
|
2072
|
+
// calls follow. Their user message must carry the truncation notice so
|
|
2073
|
+
// the repair LLM stops treating a cut fragment as a misunderstanding.
|
|
2074
|
+
const repairCalls = mockComplete.mock.calls.slice(1);
|
|
2075
|
+
expect(repairCalls.length).toBeGreaterThan(0);
|
|
2076
|
+
const firstRepairPrompt = repairPromptFromCall(repairCalls[0]);
|
|
2077
|
+
expect(firstRepairPrompt).toContain('TRUNCATION NOTICE');
|
|
2078
|
+
expect(firstRepairPrompt).toContain('finish_reason=length');
|
|
2079
|
+
});
|
|
2080
|
+
it('truncation notice absent when finish metadata shows a clean stop (repair prompt unchanged)', async () => {
|
|
2081
|
+
mockComplete.mockResolvedValueOnce(makeAssistantMessage('{"unexpected":true}'));
|
|
2082
|
+
mockComplete.mockResolvedValue(makeAssistantMessage('still not json'));
|
|
2083
|
+
const adapter = makeAdapter();
|
|
2084
|
+
try {
|
|
2085
|
+
await adapter.startRun(makeSchemaInput());
|
|
2086
|
+
}
|
|
2087
|
+
catch {
|
|
2088
|
+
// output_invalid expected — assertion is on the prompt below.
|
|
2089
|
+
}
|
|
2090
|
+
const repairCalls = mockComplete.mock.calls.slice(1);
|
|
2091
|
+
expect(repairCalls.length).toBeGreaterThan(0);
|
|
2092
|
+
const firstRepairPrompt = repairPromptFromCall(repairCalls[0]);
|
|
2093
|
+
expect(firstRepairPrompt).toBeDefined();
|
|
2094
|
+
expect(firstRepairPrompt).not.toContain('TRUNCATION NOTICE');
|
|
2095
|
+
});
|
|
2096
|
+
});
|
|
1940
2097
|
});
|
|
1941
2098
|
//# sourceMappingURL=pi-ai-runtime-adapter.test.js.map
|