@@ -28,8 +28,8 @@ vi.mock('../../src/utils/userid_cache.js', () => ({
2828 * Default mode `get-actor-run` returns: runId, actorId, status, storages, summary, nextStep
2929 * — with no inlined dataset items or KV record bodies, except RAG Web Browser's reserved `TIP` key.
3030 * Tests cover shape invariants and the branching status templates (SUCCEEDED, TIMED-OUT).
31- * Pure-template states (READY, RUNNING, TIMING-OUT, ABORTING, FAILED, ABORTED) are intentionally
32- * not asserted here — see the comment above `describe('buildStatusTemplate', ...)` below.
31+ * Pure-template states are asserted only where a rule applies ( FAILED and ABORTED only for their
32+ * nextStep) — see the comment above `describe('buildStatusTemplate', ...)` below.
3333 */
3434
3535const ACTOR = { username : 'apify' , name : 'rag-web-browser' } ;
@@ -130,6 +130,40 @@ describe('get-actor-run default response', () => {
130130 } ) ;
131131 } ) ;
132132
133+ it . each ( [
134+ [
135+ 'SUCCEEDED' ,
136+ { defaultDatasetId : undefined } ,
137+ 'Inspect statusMessage and stats in this response. If the user wants another attempt, adjust the input before retrying.' ,
138+ ] ,
139+ [
140+ 'FAILED' ,
141+ { exitCode : 1 } ,
142+ 'Diagnose using statusMessage and exitCode in this response. If the user wants another attempt, fix the cause before retrying.' ,
143+ ] ,
144+ [
145+ 'ABORTED' ,
146+ { } ,
147+ "Items written before the abort, if any, remain in the run's default dataset (datasetId=dataset-xyz). Retry only if the user wants another run." ,
148+ ] ,
149+ ] ) (
150+ 'returns a nextStep that leaves a retry to the user for %s without dataset items' ,
151+ async ( status , overrides , nextStep ) => {
152+ const run = mockSucceededRun ( { status, ...overrides } ) ;
153+ const result = await ( getActorRun as HelperTool ) . call (
154+ stubToolCallContext (
155+ { runId : 'run-1' , waitSecs : 0 } ,
156+ stubClient ( { run, dataset : mockDataset ( { itemCount : 0 } ) } ) ,
157+ ) ,
158+ ) ;
159+
160+ const { structuredContent, content } = result as TextToolResult & { structuredContent : RunResponse } ;
161+ expect ( structuredContent . nextStep ) . toBe ( nextStep ) ;
162+ expect ( content [ 1 ] . text ) . toContain ( nextStep ) ;
163+ expect ( content [ 1 ] . text ) . toContain ( 'the user wants' ) ;
164+ } ,
165+ ) ;
166+
133167 it ( 'surfaces dataset inflatedBytes from stats in structuredContent' , async ( ) => {
134168 // The single-dataset GET does not return `inflatedBytes` (only the dataset-list endpoint does);
135169 // this stub injects it to verify the wiring.
@@ -426,12 +460,12 @@ describe('get-actor-run default response', () => {
426460 expect ( structuredContent . storages . datasets ?. default . itemCount ) . toBeUndefined ( ) ;
427461 expect ( structuredContent . storages . datasets ?. default . fields ) . toBeUndefined ( ) ;
428462
429- // nextStep points at get-dataset-items, not the "no output / re-run " branch.
463+ // nextStep points at get-dataset-items, not the "no output" branch.
430464 expect ( structuredContent . nextStep ) . toContain ( 'get-dataset-items' ) ;
431465 expect ( structuredContent . nextStep ) . toContain ( 'datasetId=dataset-xyz' ) ;
432- // Narrative (content[1]) avoids the "re-run " branch wording.
466+ // Narrative (content[1]) avoids the "no output " branch wording.
433467 expect ( content [ 1 ] . text ) . not . toContain ( 'No dataset items and no key-value records were found' ) ;
434- expect ( content [ 1 ] . text ) . not . toMatch ( / r e - r u n / i ) ;
468+ expect ( content [ 1 ] . text ) . not . toContain ( 'No dataset items found' ) ;
435469 } ) ;
436470
437471 it ( 'degrades gracefully when KV listKeys fails: keeps dataset, omits KV' , async ( ) => {
@@ -1087,9 +1121,10 @@ const kvWithRecords: RunKeyValueStore = { id: 'kv-1', keys: ['result-a', 'result
10871121/**
10881122 * Tests below cover real branching in buildSucceededSummaryNextStep / buildTimedOutSummaryNextStep
10891123 * (3 branches each) and the dataset-vs-KV priority + truncation-labelling rules. Pure template
1090- * re-statements (READY/RUNNING/TIMING-OUT/ABORTING/FAILED/ABORTED) are deliberately not tested
1091- * here — they would just spell-check the format string and would change in lockstep with any
1092- * wording tweak. Default-mode integration coverage exercises the dispatch path.
1124+ * re-statements (READY/RUNNING/TIMING-OUT/ABORTING) are deliberately not tested here — they would
1125+ * just spell-check the format string and would change in lockstep with any wording tweak. FAILED
1126+ * and ABORTED are pinned because their nextStep must not ask for a new run (#1559). Default-mode
1127+ * integration coverage exercises the dispatch path.
10931128 */
10941129describe ( 'buildStatusTemplate' , ( ) => {
10951130 it ( 'SUCCEEDED with dataset items routes to dataset-items nextStep' , ( ) => {
@@ -1163,7 +1198,31 @@ describe('buildStatusTemplate', () => {
11631198 const t = buildStatusSummaryNextStep ( { run : makeRun ( 'SUCCEEDED' ) } ) ;
11641199 expect ( t . summary ) . toContain ( 'No dataset items found' ) ;
11651200 expect ( t . summary ) . not . toContain ( 'Key-value store' ) ;
1166- expect ( t . nextStep ) . toContain ( 're-run' ) ;
1201+ expect ( t . nextStep ) . toBe (
1202+ 'Inspect statusMessage and stats in this response. If the user wants another attempt, adjust the input before retrying.' ,
1203+ ) ;
1204+ } ) ;
1205+
1206+ it . each ( [
1207+ [
1208+ 'FAILED' ,
1209+ 'Diagnose using statusMessage and exitCode in this response. If the user wants another attempt, fix the cause before retrying.' ,
1210+ ] ,
1211+ [
1212+ 'ABORTED' ,
1213+ "Items written before the abort, if any, remain in the run's default dataset (datasetId=ds-1). Retry only if the user wants another run." ,
1214+ ] ,
1215+ ] ) ( '%s nextStep leaves a retry to the user and names no tool' , ( status , expected ) => {
1216+ const t = buildStatusSummaryNextStep ( { run : makeRun ( status ) , dataset : datasetWithItems } ) ;
1217+ expect ( t . nextStep ) . toBe ( expected ) ;
1218+ expect ( t . nextStep ) . toContain ( 'the user wants' ) ;
1219+ } ) ;
1220+
1221+ it ( 'ABORTED nextStep without a dataset gives no datasetId' , ( ) => {
1222+ const t = buildStatusSummaryNextStep ( { run : makeRun ( 'ABORTED' ) } ) ;
1223+ expect ( t . nextStep ) . toBe (
1224+ "Items written before the abort, if any, remain in the run's default dataset. Retry only if the user wants another run." ,
1225+ ) ;
11671226 } ) ;
11681227
11691228 it ( 'SUCCEEDED with both dataset items and KV records picks dataset for nextStep, mentions both in summary' , ( ) => {
0 commit comments