@possumtech/rummy 2.0.0 → 2.1.0

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.
Files changed (117) hide show
  1. package/.env.example +31 -5
  2. package/BENCH_ENVIRONMENT.md +230 -0
  3. package/CLIENT_INTERFACE.md +396 -0
  4. package/PLUGINS.md +93 -1
  5. package/SPEC.md +389 -28
  6. package/bin/postinstall.js +2 -2
  7. package/bin/rummy.js +2 -2
  8. package/last_run.txt +5617 -0
  9. package/migrations/001_initial_schema.sql +2 -1
  10. package/package.json +13 -9
  11. package/scriptify/ask_run.js +77 -0
  12. package/scriptify/cache_probe.js +66 -0
  13. package/scriptify/cache_probe_grok.js +74 -0
  14. package/service.js +22 -11
  15. package/src/agent/AgentLoop.js +62 -157
  16. package/src/agent/ContextAssembler.js +2 -9
  17. package/src/agent/Entries.js +54 -98
  18. package/src/agent/ProjectAgent.js +4 -11
  19. package/src/agent/TurnExecutor.js +48 -83
  20. package/src/agent/XmlParser.js +247 -273
  21. package/src/agent/budget.js +5 -28
  22. package/src/agent/config.js +38 -0
  23. package/src/agent/errors.js +7 -13
  24. package/src/agent/httpStatus.js +1 -19
  25. package/src/agent/known_queries.sql +1 -1
  26. package/src/agent/known_store.sql +12 -2
  27. package/src/agent/materializeContext.js +15 -18
  28. package/src/agent/pathEncode.js +5 -0
  29. package/src/agent/rummyHome.js +9 -0
  30. package/src/agent/runs.sql +37 -0
  31. package/src/agent/tokens.js +7 -7
  32. package/src/hooks/HookRegistry.js +1 -16
  33. package/src/hooks/Hooks.js +8 -33
  34. package/src/hooks/PluginContext.js +3 -21
  35. package/src/hooks/RpcRegistry.js +1 -4
  36. package/src/hooks/RummyContext.js +6 -16
  37. package/src/hooks/ToolRegistry.js +5 -15
  38. package/src/llm/LlmProvider.js +41 -33
  39. package/src/llm/errors.js +41 -4
  40. package/src/llm/openaiStream.js +125 -0
  41. package/src/llm/retry.js +109 -0
  42. package/src/plugins/budget/budget.js +55 -76
  43. package/src/plugins/cli/README.md +87 -0
  44. package/src/plugins/cli/bin.js +61 -0
  45. package/src/plugins/cli/cli.js +120 -0
  46. package/src/plugins/env/README.md +2 -1
  47. package/src/plugins/env/env.js +4 -6
  48. package/src/plugins/env/envDoc.md +2 -2
  49. package/src/plugins/error/error.js +23 -23
  50. package/src/plugins/file/file.js +2 -22
  51. package/src/plugins/get/get.js +12 -34
  52. package/src/plugins/get/getDoc.md +8 -6
  53. package/src/plugins/hedberg/edits.js +1 -11
  54. package/src/plugins/hedberg/hedberg.js +3 -26
  55. package/src/plugins/hedberg/normalize.js +1 -5
  56. package/src/plugins/hedberg/patterns.js +4 -15
  57. package/src/plugins/hedberg/sed.js +1 -7
  58. package/src/plugins/helpers.js +28 -20
  59. package/src/plugins/index.js +25 -41
  60. package/src/plugins/instructions/README.md +18 -0
  61. package/src/plugins/instructions/instructions.js +97 -38
  62. package/src/plugins/instructions/instructions.md +24 -15
  63. package/src/plugins/instructions/instructions_104.md +5 -4
  64. package/src/plugins/instructions/instructions_105.md +29 -36
  65. package/src/plugins/instructions/instructions_106.md +22 -0
  66. package/src/plugins/instructions/instructions_107.md +17 -0
  67. package/src/plugins/instructions/instructions_108.md +0 -8
  68. package/src/plugins/known/README.md +26 -6
  69. package/src/plugins/known/known.js +37 -34
  70. package/src/plugins/log/README.md +2 -2
  71. package/src/plugins/log/log.js +27 -34
  72. package/src/plugins/ollama/ollama.js +50 -66
  73. package/src/plugins/openai/openai.js +26 -44
  74. package/src/plugins/openrouter/openrouter.js +28 -52
  75. package/src/plugins/policy/README.md +8 -2
  76. package/src/plugins/policy/policy.js +8 -21
  77. package/src/plugins/prompt/README.md +22 -0
  78. package/src/plugins/prompt/prompt.js +14 -16
  79. package/src/plugins/rm/rm.js +5 -2
  80. package/src/plugins/rm/rmDoc.md +4 -4
  81. package/src/plugins/rpc/README.md +2 -1
  82. package/src/plugins/rpc/rpc.js +62 -48
  83. package/src/plugins/set/README.md +5 -1
  84. package/src/plugins/set/set.js +23 -33
  85. package/src/plugins/set/setDoc.md +1 -1
  86. package/src/plugins/sh/README.md +2 -1
  87. package/src/plugins/sh/sh.js +5 -11
  88. package/src/plugins/sh/shDoc.md +2 -2
  89. package/src/plugins/stream/README.md +6 -5
  90. package/src/plugins/stream/stream.js +6 -35
  91. package/src/plugins/telemetry/telemetry.js +26 -19
  92. package/src/plugins/think/think.js +4 -7
  93. package/src/plugins/unknown/unknown.js +8 -13
  94. package/src/plugins/update/update.js +42 -25
  95. package/src/plugins/update/updateDoc.md +3 -3
  96. package/src/plugins/xai/xai.js +30 -20
  97. package/src/plugins/yolo/yolo.js +159 -0
  98. package/src/server/ClientConnection.js +17 -47
  99. package/src/server/SocketServer.js +14 -14
  100. package/src/server/protocol.js +1 -10
  101. package/src/sql/functions/slugify.js +5 -7
  102. package/src/sql/v_model_context.sql +4 -11
  103. package/turns/cli_1777462658211/turn_001.txt +772 -0
  104. package/turns/cli_1777462658211/turn_002.txt +606 -0
  105. package/turns/cli_1777462658211/turn_003.txt +667 -0
  106. package/turns/cli_1777462658211/turn_004.txt +297 -0
  107. package/turns/cli_1777462658211/turn_005.txt +301 -0
  108. package/turns/cli_1777462658211/turn_006.txt +262 -0
  109. package/turns/cli_1777465095132/turn_001.txt +715 -0
  110. package/turns/cli_1777465095132/turn_002.txt +236 -0
  111. package/turns/cli_1777465095132/turn_003.txt +287 -0
  112. package/turns/cli_1777465095132/turn_004.txt +694 -0
  113. package/turns/cli_1777465095132/turn_005.txt +422 -0
  114. package/turns/cli_1777465095132/turn_006.txt +365 -0
  115. package/turns/cli_1777465095132/turn_007.txt +885 -0
  116. package/turns/cli_1777465095132/turn_008.txt +1277 -0
  117. package/turns/cli_1777465095132/turn_009.txt +736 -0
@@ -1,4 +1,3 @@
1
- import { computeBudget } from "./budget.js";
2
1
  import msg from "./messages.js";
3
2
 
4
3
  const HTTP_TO_RUN_STATE = {
@@ -31,20 +30,13 @@ export default class AgentLoop {
31
30
  if (active) active.controller.abort();
32
31
  }
33
32
 
34
- /**
35
- * Abort every in-flight run and wait for each drain to settle.
36
- * Called from server close / client teardown so the process can
37
- * exit cleanly instead of leaving detached kickoff Promises
38
- * pinning the event loop.
39
- */
33
+ // Abort all in-flight runs and drain; rejections were already surfaced to original awaiters.
40
34
  async abortAll() {
41
35
  const promises = [];
42
36
  for (const { controller, promise } of this.#activeRuns.values()) {
43
37
  controller.abort();
44
38
  promises.push(promise);
45
39
  }
46
- // allSettled: drain waits for every run to finish; rejections are
47
- // already surfaced to whoever awaited the original run() call.
48
40
  await Promise.allSettled(promises);
49
41
  }
50
42
 
@@ -56,6 +48,24 @@ export default class AgentLoop {
56
48
  return `Turn ${turn}/${maxTurns}`;
57
49
  }
58
50
 
51
+ async #emitCompleted(hook, projectId, runId, out) {
52
+ const s = await this.#db.get_run_summary.get({ id: runId });
53
+ await hook.completed.emit({
54
+ projectId,
55
+ ...out,
56
+ model: s.model,
57
+ turns: s.turns,
58
+ cost: s.cost,
59
+ tokens: {
60
+ prompt: s.prompt_tokens,
61
+ cached: s.cached_tokens,
62
+ completion: s.completion_tokens,
63
+ reasoning: s.reasoning_tokens,
64
+ total: s.total_tokens,
65
+ },
66
+ });
67
+ }
68
+
59
69
  async #setRunStatus(runId, alias, httpStatus) {
60
70
  await this.#db.update_run_status.run({ id: runId, status: httpStatus });
61
71
  const state = HTTP_TO_RUN_STATE[httpStatus];
@@ -68,88 +78,6 @@ export default class AgentLoop {
68
78
  });
69
79
  }
70
80
 
71
- async #emitRunState({
72
- projectId,
73
- runId,
74
- alias,
75
- turn,
76
- status,
77
- contextSize,
78
- result = null,
79
- }) {
80
- if (!contextSize) throw new Error("#emitRunState: contextSize is required");
81
- const runUsage = await this.#db.get_run_usage.get({ run_id: runId });
82
- const history = await this.#entries.getLog(runId);
83
- const unknowns = await this.#entries.getUnknowns(runId);
84
- const latestSummary = history
85
- .filter((e) => {
86
- // Updates are under the unified log namespace at
87
- // log://turn_N/update/<slug>. Match by path pattern rather
88
- // than scheme (scheme is now "log" for all log entries).
89
- if (!/^log:\/\/turn_\d+\/update\//.test(e.path)) return false;
90
- const attrs =
91
- typeof e.attributes === "string"
92
- ? JSON.parse(e.attributes)
93
- : e.attributes;
94
- return attrs?.status === 200;
95
- })
96
- .at(-1);
97
-
98
- // Always emit complete telemetry. When we don't have a fresh turn
99
- // result (abort/max-turns/crash), read the last turn's context
100
- // tokens from the DB instead. Both code paths compute a real
101
- // budget from real data — never undefined, never invented.
102
- const rows = await this.#db.get_turn_context.all({
103
- run_id: runId,
104
- turn,
105
- });
106
- let totalTokens;
107
- if (result) {
108
- totalTokens = result.assembledTokens;
109
- } else {
110
- // No fresh turn result — this happens on abort/max-turns/crash
111
- // emits that fire before any turn executed, or after a turn
112
- // that never produced tokens. Read the last turn's assembled
113
- // context_tokens from the DB; absent means no turn ran yet
114
- // (zero is the truth, not a fallback).
115
- const lastCtx = await this.#db.get_last_context_tokens.get({
116
- run_id: runId,
117
- });
118
- totalTokens = lastCtx ? lastCtx.context_tokens : 0;
119
- }
120
- const budget = computeBudget({ rows, contextSize, totalTokens });
121
-
122
- await this.#hooks.run.state.emit({
123
- projectId,
124
- run: alias,
125
- turn,
126
- status,
127
- summary: latestSummary?.body,
128
- history,
129
- unknowns: unknowns.map((u) => ({ path: u.path, body: u.body })),
130
- telemetry: {
131
- modelAlias: result?.modelAlias,
132
- model: result?.model,
133
- temperature: result?.temperature,
134
- context_size: contextSize,
135
- context_tokens: totalTokens,
136
- ceiling: budget.ceiling,
137
- token_usage: budget.tokenUsage,
138
- tokens_free: budget.tokensFree,
139
- prompt_tokens: runUsage.prompt_tokens,
140
- cached_tokens: runUsage.cached_tokens,
141
- completion_tokens: runUsage.completion_tokens,
142
- reasoning_tokens: runUsage.reasoning_tokens,
143
- total_tokens: runUsage.total_tokens,
144
- cost: runUsage.cost,
145
- context_distribution: await this.#db.get_turn_distribution.all({
146
- run_id: runId,
147
- turn,
148
- }),
149
- },
150
- });
151
- }
152
-
153
81
  async #writeRunEntry(
154
82
  runId,
155
83
  alias,
@@ -227,7 +155,6 @@ export default class AgentLoop {
227
155
  const existing = this.#activeRuns.get(existingRun.id);
228
156
  if (existing) existing.controller.abort();
229
157
 
230
- // Clean up stale proposals from interrupted runs
231
158
  const unresolved = await this.#entries.getUnresolved(existingRun.id);
232
159
  for (const u of unresolved) {
233
160
  await this.#entries.set({
@@ -240,7 +167,6 @@ export default class AgentLoop {
240
167
  }
241
168
  return { runId: existingRun.id, alias: existingRun.alias };
242
169
  }
243
- // Client-specified alias for a brand-new run — accept it verbatim.
244
170
  }
245
171
 
246
172
  const alias = run ? run : await this.#generateAlias(requestedModel);
@@ -287,10 +213,13 @@ export default class AgentLoop {
287
213
  if (!project)
288
214
  throw new Error(msg("error.project_not_found", { projectId }));
289
215
 
290
- const noRepo = options?.noRepo === true;
291
- const noInteraction = options?.noInteraction === true;
292
- const noWeb = options?.noWeb === true;
293
- const noProposals = options?.noProposals === true;
216
+ const noRepo = options?.noRepo ?? process.env.RUMMY_NO_REPO === "1";
217
+ const noInteraction =
218
+ options?.noInteraction ?? process.env.RUMMY_NO_INTERACTION === "1";
219
+ const noWeb = options?.noWeb ?? process.env.RUMMY_NO_WEB === "1";
220
+ const noProposals =
221
+ options?.noProposals ?? process.env.RUMMY_NO_PROPOSALS === "1";
222
+ const yolo = options?.yolo ?? process.env.RUMMY_YOLO === "1";
294
223
  const requestedModel = model;
295
224
 
296
225
  const runInfo = await this.ensureRun(
@@ -314,6 +243,7 @@ export default class AgentLoop {
314
243
  noInteraction,
315
244
  noWeb,
316
245
  noProposals,
246
+ yolo,
317
247
  temperature: options?.temperature,
318
248
  }),
319
249
  });
@@ -322,8 +252,7 @@ export default class AgentLoop {
322
252
  return { run: currentAlias, status: 100 };
323
253
  }
324
254
 
325
- // Allocate the controller + Promise pair here so `abortAll` can
326
- // reach both — abort the controller, await the Promise's drain.
255
+ // Pair controller + Promise so abortAll can both signal and await drain.
327
256
  const controller = new AbortController();
328
257
  const promise = this.#drainQueue(
329
258
  currentRunId,
@@ -367,6 +296,7 @@ export default class AgentLoop {
367
296
  noInteraction = false,
368
297
  noWeb = false,
369
298
  noProposals = false,
299
+ yolo = false,
370
300
  } = loopConfig;
371
301
 
372
302
  let result;
@@ -384,6 +314,7 @@ export default class AgentLoop {
384
314
  noInteraction,
385
315
  noWeb,
386
316
  noProposals,
317
+ yolo,
387
318
  options: { ...options, temperature: loopConfig.temperature },
388
319
  hook,
389
320
  signal: controller.signal,
@@ -448,6 +379,7 @@ export default class AgentLoop {
448
379
  noInteraction,
449
380
  noWeb,
450
381
  noProposals,
382
+ yolo,
451
383
  options,
452
384
  hook,
453
385
  signal,
@@ -470,7 +402,7 @@ export default class AgentLoop {
470
402
  });
471
403
 
472
404
  let loopIteration = 0;
473
- const MAX_LOOP_ITERATIONS = Number(process.env.RUMMY_MAX_TURNS);
405
+ const MAX_LOOP_TURNS = Number(process.env.RUMMY_MAX_LOOP_TURNS);
474
406
 
475
407
  await this.#hooks.loop.started.emit({
476
408
  runId: currentRunId,
@@ -480,31 +412,23 @@ export default class AgentLoop {
480
412
  });
481
413
 
482
414
  try {
483
- while (loopIteration < MAX_LOOP_ITERATIONS) {
415
+ while (loopIteration < MAX_LOOP_TURNS) {
484
416
  if (signal.aborted) {
485
417
  console.error(
486
418
  `[LOOP] ${currentAlias} iter=${loopIteration} ABORT via signal`,
487
419
  );
488
420
  await this.#setRunStatus(currentRunId, currentAlias, 499);
489
- await this.#emitRunState({
490
- projectId,
491
- runId: currentRunId,
492
- alias: currentAlias,
493
- turn: loopIteration,
494
- status: 499,
495
- contextSize,
496
- });
497
421
  const out = {
498
422
  run: currentAlias,
499
423
  status: 499,
500
424
  turn: loopIteration,
501
425
  };
502
- await hook.completed.emit({ projectId, ...out });
426
+ await this.#emitCompleted(hook, projectId, currentRunId, out);
503
427
  return out;
504
428
  }
505
429
  loopIteration++;
506
430
  console.error(
507
- `[LOOP] ${currentAlias} iter=${loopIteration} ENTER (max=${MAX_LOOP_ITERATIONS})`,
431
+ `[LOOP] ${currentAlias} iter=${loopIteration} ENTER (max=${MAX_LOOP_TURNS})`,
508
432
  );
509
433
 
510
434
  let turnPrompt;
@@ -513,7 +437,7 @@ export default class AgentLoop {
513
437
  } else {
514
438
  turnPrompt = this.#buildContinuationPrompt(
515
439
  loopIteration,
516
- MAX_LOOP_ITERATIONS,
440
+ MAX_LOOP_TURNS,
517
441
  );
518
442
  }
519
443
 
@@ -534,6 +458,7 @@ export default class AgentLoop {
534
458
  noWeb,
535
459
  noInteraction,
536
460
  noProposals,
461
+ yolo,
537
462
  toolSet,
538
463
  contextSize,
539
464
  options: { ...options, isContinuation: loopIteration > 1 },
@@ -557,15 +482,6 @@ export default class AgentLoop {
557
482
  `[LOOP] ${currentAlias} iter=${loopIteration} verdict: continue=${verdict.continue} status=${vStatus} reason=${vReason}`,
558
483
  );
559
484
 
560
- await this.#emitRunState({
561
- projectId,
562
- runId: currentRunId,
563
- alias: currentAlias,
564
- turn: result.turn,
565
- status: verdict.continue ? 102 : verdict.status,
566
- contextSize,
567
- result,
568
- });
569
485
  await this.#hooks.run.step.completed.emit({
570
486
  projectId,
571
487
  run: currentAlias,
@@ -592,41 +508,24 @@ export default class AgentLoop {
592
508
  turn: result.turn,
593
509
  reason: verdict.reason,
594
510
  };
595
- await hook.completed.emit({ projectId, ...out });
511
+ await this.#emitCompleted(hook, projectId, currentRunId, out);
596
512
  return out;
597
513
  }
598
514
 
599
- // MAX_TURNS exhaustion without a terminal update is abandonment.
600
515
  console.error(
601
- `[LOOP] ${currentAlias} hit MAX_LOOP_ITERATIONS=${MAX_LOOP_ITERATIONS} — abandoning at 499`,
516
+ `[LOOP] ${currentAlias} hit MAX_LOOP_TURNS=${MAX_LOOP_TURNS} — abandoning at 499`,
602
517
  );
603
518
  await this.#setRunStatus(currentRunId, currentAlias, 499);
604
- await this.#emitRunState({
605
- projectId,
606
- runId: currentRunId,
607
- alias: currentAlias,
608
- turn: loopIteration,
609
- status: 499,
610
- contextSize,
611
- });
612
519
  const out = {
613
520
  run: currentAlias,
614
521
  status: 499,
615
522
  turn: loopIteration,
616
523
  };
617
- await hook.completed.emit({ projectId, ...out });
524
+ await this.#emitCompleted(hook, projectId, currentRunId, out);
618
525
  return out;
619
526
  } catch (err) {
620
527
  const status = signal.aborted ? 499 : 500;
621
528
  await this.#setRunStatus(currentRunId, currentAlias, status);
622
- await this.#emitRunState({
623
- projectId,
624
- runId: currentRunId,
625
- alias: currentAlias,
626
- turn: loopIteration,
627
- status,
628
- contextSize,
629
- });
630
529
  if (status === 500) {
631
530
  await this.#hooks.error.log.emit({
632
531
  store: this.#entries,
@@ -638,7 +537,7 @@ export default class AgentLoop {
638
537
  }
639
538
  const out = { run: currentAlias, status, turn: loopIteration };
640
539
  if (status === 500) out.error = err.message;
641
- await hook.completed.emit({ projectId, ...out });
540
+ await this.#emitCompleted(hook, projectId, currentRunId, out);
642
541
  return out;
643
542
  } finally {
644
543
  await this.#hooks.loop.completed.emit({
@@ -678,11 +577,7 @@ export default class AgentLoop {
678
577
  db: this.#db,
679
578
  entries: this.#entries,
680
579
  });
681
- // Report the CURRENT run status (typically 102 mid-run) so the
682
- // client's dispatch handler doesn't mistake a successful
683
- // resolve's HTTP-style 200 ack for a terminal run status and
684
- // prematurely close the document. Real terminal state comes
685
- // from the run/state notification at end-of-turn.
580
+ // Return current run status (not 200) so client doesn't close on resolve ack.
686
581
  return { run: runAlias, status: runRow.status };
687
582
  }
688
583
 
@@ -702,8 +597,7 @@ export default class AgentLoop {
702
597
  entries: this.#entries,
703
598
  };
704
599
 
705
- // Plugins veto acceptance (e.g. readonly) via proposal.accepting.
706
- // First veto wins: state=failed with plugin-supplied outcome + body.
600
+ // First plugin veto wins via proposal.accepting (e.g. readonly).
707
601
  if (action === "accept") {
708
602
  const veto = await this.#hooks.proposal.accepting.filter(null, ctx);
709
603
  if (veto?.allow === false) {
@@ -718,9 +612,7 @@ export default class AgentLoop {
718
612
  }
719
613
  }
720
614
 
721
- // Compose the resolved body. Default is output || "". Plugins may
722
- // override via proposal.content (e.g. set prefers the existing
723
- // proposed body from the log entry).
615
+ // proposal.content override lets plugins prefer the proposed body (e.g. set).
724
616
  const defaultBody = output ? output : "";
725
617
  const resolvedBody = await this.#hooks.proposal.content.filter(
726
618
  defaultBody,
@@ -745,13 +637,11 @@ export default class AgentLoop {
745
637
  : this.#hooks.proposal.rejected;
746
638
  await event.emit({ ...ctx, resolvedBody });
747
639
 
748
- // Same rationale as the reject path: return current run status
749
- // (102 mid-run) rather than a hardcoded 200 so the nvim client
750
- // doesn't treat the RPC ack as a terminal signal.
640
+ // Return current run status (not 200) so client doesn't close on resolve ack.
751
641
  return { run: runAlias, status: runRow.status };
752
642
  }
753
643
 
754
- async inject(runAlias, message, mode) {
644
+ async inject(runAlias, message, mode, options = {}) {
755
645
  if (mode !== "ask" && mode !== "act") {
756
646
  throw new Error(
757
647
  `inject: mode is required and must be "ask" or "act" (got ${JSON.stringify(mode)})`,
@@ -761,6 +651,14 @@ export default class AgentLoop {
761
651
  if (!runRow)
762
652
  throw new Error(msg("error.run_not_found", { runId: runAlias }));
763
653
 
654
+ const noRepo = options?.noRepo ?? process.env.RUMMY_NO_REPO === "1";
655
+ const noInteraction =
656
+ options?.noInteraction ?? process.env.RUMMY_NO_INTERACTION === "1";
657
+ const noWeb = options?.noWeb ?? process.env.RUMMY_NO_WEB === "1";
658
+ const noProposals =
659
+ options?.noProposals ?? process.env.RUMMY_NO_PROPOSALS === "1";
660
+ const yolo = options?.yolo ?? process.env.RUMMY_YOLO === "1";
661
+
764
662
  const nextTurn = runRow.next_turn;
765
663
 
766
664
  await this.#entries.set({
@@ -784,7 +682,14 @@ export default class AgentLoop {
784
682
  mode,
785
683
  model: runRow.model,
786
684
  prompt: message,
787
- config: "{}",
685
+ config: JSON.stringify({
686
+ noRepo,
687
+ noInteraction,
688
+ noWeb,
689
+ noProposals,
690
+ yolo,
691
+ temperature: options?.temperature,
692
+ }),
788
693
  });
789
694
 
790
695
  const projectId = runRow.project_id;
@@ -1,8 +1,4 @@
1
- /**
2
- * Thin orchestrator. Computes loopStartTurn from the rows,
3
- * then invokes assembly.system and assembly.user filter chains.
4
- * All rendering logic lives in plugins.
5
- */
1
+ // Orchestrates assembly.system / assembly.user filter chains; plugins do all rendering.
6
2
  export default class ContextAssembler {
7
3
  static async assembleFromTurnContext(
8
4
  rows,
@@ -10,15 +6,13 @@ export default class ContextAssembler {
10
6
  type = "ask",
11
7
  systemPrompt = "",
12
8
  contextSize = 0,
13
- demoted = [],
14
9
  toolSet = null,
15
10
  lastContextTokens = 0,
16
11
  turn = 1,
17
12
  } = {},
18
13
  hooks,
19
14
  ) {
20
- // Find loop boundary from active prompt. Absent on turn 1 before
21
- // the prompt plugin's turn.started handler has run.
15
+ // Loop boundary from active prompt; absent on turn 1 before prompt plugin's turn.started.
22
16
  const promptEntry = rows.findLast(
23
17
  (r) => r.category === "prompt" && r.scheme === "prompt",
24
18
  );
@@ -31,7 +25,6 @@ export default class ContextAssembler {
31
25
  type,
32
26
  contextSize,
33
27
  lastContextTokens,
34
- demoted,
35
28
  toolSet,
36
29
  turn,
37
30
  };