@poncho-ai/cli 0.38.0 → 0.38.1

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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @poncho-ai/cli@0.38.0 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
2
+ > @poncho-ai/cli@0.38.1 build /home/runner/work/poncho-ai/poncho-ai/packages/cli
3
3
  > tsup src/index.ts src/cli.ts --format esm --dts
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -7,12 +7,12 @@
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  ESM Build start
10
- ESM dist/index.js 3.10 KB
11
10
  ESM dist/cli.js 94.00 B
12
- ESM dist/chunk-U643TWFX.js 601.19 KB
13
- ESM dist/run-interactive-ink-CE7U47S5.js 23.38 KB
14
- ESM ⚡️ Build success in 105ms
11
+ ESM dist/index.js 3.10 KB
12
+ ESM dist/run-interactive-ink-UKPUGCDW.js 23.38 KB
13
+ ESM dist/chunk-W7SQVUB4.js 601.92 KB
14
+ ESM ⚡️ Build success in 79ms
15
15
  DTS Build start
16
- DTS ⚡️ Build success in 4659ms
16
+ DTS ⚡️ Build success in 4135ms
17
17
  DTS dist/cli.d.ts 20.00 B
18
- DTS dist/index.d.ts 12.83 KB
18
+ DTS dist/index.d.ts 13.24 KB
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @poncho-ai/cli
2
2
 
3
+ ## 0.38.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [`d6248c8`](https://github.com/cesr/poncho-ai/commit/d6248c8b6d22e0fd0becde9e31dff7c12c724d84) Thanks [@cesr](https://github.com/cesr)! - fix(cli, harness): unify turn-parameter assembly so `conversation_recall` works everywhere
8
+
9
+ The recall tool relies on three context parameters (`__conversationRecallCorpus`,
10
+ `__conversationListFn`, `__conversationFetchFn`) that were only injected for
11
+ user-initiated HTTP turns. Cron, reminder, messaging-adapter, chat-continuation,
12
+ subagent-callback, and tool-approval-resume runs all built their own
13
+ `runInput.parameters` object and silently omitted these — causing
14
+ `conversation_recall` to throw "not available in this environment" or return
15
+ empty results depending on the call mode.
16
+
17
+ Introduces a single `buildTurnParameters(conversation, opts)` helper in the CLI
18
+ that owns context-parameter assembly (recall functions, `__activeConversationId`,
19
+ `__ownerId`, messaging metadata, tool-result archive). HTTP, messaging, and
20
+ cron/reminder paths now go through it. The harness orchestrator's three
21
+ internal turn sites (chat continuation, subagent-callback resume, tool-approval
22
+ resume) now call the existing `hooks.buildRecallParams` so they pick up the
23
+ recall functions too.
24
+
25
+ - [#101](https://github.com/cesr/poncho-ai/pull/101) [`7cc2fb5`](https://github.com/cesr/poncho-ai/commit/7cc2fb592bf11b79916df5831598a991f1ac9c0c) Thanks [@cesr](https://github.com/cesr)! - fix(web-ui): thread panel displays anchor message + replies, not full snapshot
26
+
27
+ Shows the anchor message you forked on, plus any replies — and that's it.
28
+ The earlier snapshot is still part of the thread's context server-side
29
+ (the agent sees the full prior conversation), but the panel only
30
+ displays what's relevant: the message you replied to, and what came
31
+ after.
32
+
33
+ Also fixes the underlying scroll bug: `.thread-panel-messages` had
34
+ `flex: 1; overflow-y: auto` but no `min-height: 0`. In flex children
35
+ the default `min-height: auto` lets the item grow to fit content, so
36
+ the messages area never shrank below its content size and the scrollbar
37
+ never engaged.
38
+
39
+ - Updated dependencies [[`244a3a3`](https://github.com/cesr/poncho-ai/commit/244a3a310c6c52f9e8535b28fb25d77829583d3f), [`d6248c8`](https://github.com/cesr/poncho-ai/commit/d6248c8b6d22e0fd0becde9e31dff7c12c724d84)]:
40
+ - @poncho-ai/harness@0.39.1
41
+
3
42
  ## 0.38.0
4
43
 
5
44
  ### Minor Changes
@@ -2280,21 +2280,12 @@ var WEB_UI_STYLES = `
2280
2280
  border-radius: 4px;
2281
2281
  }
2282
2282
  .thread-panel-close:hover { color: var(--fg); }
2283
- .thread-panel-parent {
2284
- padding: 12px 16px;
2285
- border-bottom: 1px solid var(--border);
2286
- background: var(--bg-bubble-user, rgba(0,0,0,0.02));
2287
- font-size: 13px;
2288
- }
2289
- .thread-panel-parent .message-row {
2290
- margin: 0;
2291
- }
2292
- .thread-panel-parent-empty {
2293
- color: var(--fg-3);
2294
- font-style: italic;
2295
- }
2296
2283
  .thread-panel-messages {
2297
- flex: 1;
2284
+ flex: 1 1 0;
2285
+ /* min-height: 0 is required so this flex item can shrink below its
2286
+ content size \u2014 without it, overflow-y:auto never engages because
2287
+ the element grows to fit all messages instead of scrolling. */
2288
+ min-height: 0;
2298
2289
  overflow-y: auto;
2299
2290
  padding: 12px 16px;
2300
2291
  }
@@ -2391,7 +2382,6 @@ var getWebUiClientScript = (markedSource2) => `
2391
2382
  open: false,
2392
2383
  threadId: null,
2393
2384
  parentMessageId: null,
2394
- parentMessage: null,
2395
2385
  messages: [],
2396
2386
  isStreaming: false,
2397
2387
  abortController: null,
@@ -2438,7 +2428,6 @@ var getWebUiClientScript = (markedSource2) => `
2438
2428
  threadPanel: $("thread-panel"),
2439
2429
  threadPanelResize: $("thread-panel-resize"),
2440
2430
  threadPanelClose: $("thread-panel-close"),
2441
- threadPanelParent: $("thread-panel-parent"),
2442
2431
  threadPanelMessages: $("thread-panel-messages"),
2443
2432
  threadComposer: $("thread-composer"),
2444
2433
  threadAttachBtn: $("thread-attach-btn"),
@@ -3769,24 +3758,6 @@ var getWebUiClientScript = (markedSource2) => `
3769
3758
  root.scrollTop = root.scrollHeight;
3770
3759
  };
3771
3760
 
3772
- const renderThreadPanelParent = () => {
3773
- const root = elements.threadPanelParent;
3774
- if (!root) return;
3775
- root.innerHTML = "";
3776
- const parent = state.threadPanel.parentMessage;
3777
- if (!parent) {
3778
- const empty = document.createElement("div");
3779
- empty.className = "thread-panel-parent-empty";
3780
- empty.textContent = "No parent context";
3781
- root.appendChild(empty);
3782
- return;
3783
- }
3784
- const col = document.createElement("div");
3785
- col.className = "messages-column";
3786
- col.appendChild(buildSimpleMessageRow(parent));
3787
- root.appendChild(col);
3788
- };
3789
-
3790
3761
  const closeThreadPanel = () => {
3791
3762
  if (state.threadPanel.abortController) {
3792
3763
  try { state.threadPanel.abortController.abort(); } catch (e) {}
@@ -3794,7 +3765,6 @@ var getWebUiClientScript = (markedSource2) => `
3794
3765
  state.threadPanel.open = false;
3795
3766
  state.threadPanel.threadId = null;
3796
3767
  state.threadPanel.parentMessageId = null;
3797
- state.threadPanel.parentMessage = null;
3798
3768
  state.threadPanel.messages = [];
3799
3769
  state.threadPanel.isStreaming = false;
3800
3770
  state.threadPanel.abortController = null;
@@ -3823,14 +3793,15 @@ var getWebUiClientScript = (markedSource2) => `
3823
3793
  const renderActiveTopForThreadPanel = (payload) => {
3824
3794
  const conv = payload.conversation || {};
3825
3795
  const allMsgs = Array.isArray(conv.messages) ? conv.messages : [];
3796
+ // Show the anchor message + replies. The earlier snapshot is still
3797
+ // part of the thread's context server-side, but the panel only
3798
+ // displays what's relevant: the message you forked on, plus what
3799
+ // came after.
3826
3800
  const snapshotLength = (conv.threadMeta && typeof conv.threadMeta.snapshotLength === "number")
3827
3801
  ? conv.threadMeta.snapshotLength
3828
3802
  : allMsgs.length;
3829
- const parent = allMsgs[snapshotLength - 1] || null;
3830
- const replies = allMsgs.slice(snapshotLength);
3831
- state.threadPanel.parentMessage = parent;
3832
- state.threadPanel.messages = replies;
3833
- renderThreadPanelParent();
3803
+ const startIdx = Math.max(0, snapshotLength - 1);
3804
+ state.threadPanel.messages = allMsgs.slice(startIdx);
3834
3805
  renderThreadPanelMessages();
3835
3806
  };
3836
3807
 
@@ -7842,7 +7813,6 @@ ${WEB_UI_STYLES}
7842
7813
  <span class="thread-panel-title">Thread</span>
7843
7814
  <button id="thread-panel-close" class="thread-panel-close" title="Close thread">&times;</button>
7844
7815
  </div>
7845
- <div id="thread-panel-parent" class="thread-panel-parent"></div>
7846
7816
  <div id="thread-panel-messages" class="thread-panel-messages messages"></div>
7847
7817
  <form id="thread-composer" class="composer thread-composer">
7848
7818
  <div class="composer-inner">
@@ -10461,19 +10431,22 @@ var normalizeMessageForClient = (message) => {
10461
10431
  }
10462
10432
  return message;
10463
10433
  };
10464
- var runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent) => {
10434
+ var runCronAgent = async (harnessRef, task, conversationId, historyMessages, toolResultArchive, onEvent, parameters, tenantId) => {
10465
10435
  const turnTimestamp = Date.now();
10466
10436
  const userMessageId = randomUUID2();
10467
10437
  const assistantId = randomUUID2();
10438
+ const finalParameters = {
10439
+ ...parameters ?? {},
10440
+ __activeConversationId: conversationId,
10441
+ [TOOL_RESULT_ARCHIVE_PARAM]: parameters?.[TOOL_RESULT_ARCHIVE_PARAM] ?? toolResultArchive ?? {}
10442
+ };
10468
10443
  const execution = await executeConversationTurn({
10469
10444
  harness: harnessRef,
10470
10445
  runInput: {
10471
10446
  task,
10472
10447
  conversationId,
10473
- parameters: {
10474
- __activeConversationId: conversationId,
10475
- [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {}
10476
- },
10448
+ tenantId: tenantId ?? void 0,
10449
+ parameters: finalParameters,
10477
10450
  messages: historyMessages
10478
10451
  },
10479
10452
  onEvent
@@ -12718,7 +12691,7 @@ var runInteractive = async (workingDir, params) => {
12718
12691
  await harness.initialize();
12719
12692
  const identity = await ensureAgentIdentity2(workingDir);
12720
12693
  try {
12721
- const { runInteractiveInk } = await import("./run-interactive-ink-CE7U47S5.js");
12694
+ const { runInteractiveInk } = await import("./run-interactive-ink-UKPUGCDW.js");
12722
12695
  await runInteractiveInk({
12723
12696
  harness,
12724
12697
  params,
@@ -13115,6 +13088,24 @@ data: ${JSON.stringify(statusPayload)}
13115
13088
  __conversationFetchFn: conversationFetchFn
13116
13089
  };
13117
13090
  };
13091
+ const buildTurnParameters = (conversation, opts = {}) => {
13092
+ return withToolResultArchiveParam({
13093
+ ...opts.bodyParameters ?? {},
13094
+ ...buildRecallParams({
13095
+ ownerId: conversation.ownerId,
13096
+ tenantId: conversation.tenantId,
13097
+ excludeConversationId: conversation.conversationId
13098
+ }),
13099
+ ...opts.messagingMetadata ? {
13100
+ __messaging_platform: opts.messagingMetadata.platform,
13101
+ __messaging_sender_id: opts.messagingMetadata.sender.id,
13102
+ __messaging_sender_name: opts.messagingMetadata.sender.name ?? "",
13103
+ __messaging_thread_id: opts.messagingMetadata.threadId
13104
+ } : {},
13105
+ __activeConversationId: conversation.conversationId,
13106
+ __ownerId: conversation.ownerId
13107
+ }, conversation);
13108
+ };
13118
13109
  const messagingRoutes = /* @__PURE__ */ new Map();
13119
13110
  const messagingRouteRegistrar = (method, path, routeHandler) => {
13120
13111
  let byMethod = messagingRoutes.get(path);
@@ -13230,9 +13221,12 @@ data: ${JSON.stringify(statusPayload)}
13230
13221
  const runInput = {
13231
13222
  task: input2.task,
13232
13223
  conversationId,
13224
+ tenantId: latestConversation?.tenantId ?? void 0,
13233
13225
  messages: historyMessages,
13234
13226
  files: input2.files,
13235
- parameters: withToolResultArchiveParam({
13227
+ parameters: latestConversation ? buildTurnParameters(latestConversation, {
13228
+ messagingMetadata: input2.metadata
13229
+ }) : withToolResultArchiveParam({
13236
13230
  ...input2.metadata ? {
13237
13231
  __messaging_platform: input2.metadata.platform,
13238
13232
  __messaging_sender_id: input2.metadata.sender.id,
@@ -13240,7 +13234,7 @@ data: ${JSON.stringify(statusPayload)}
13240
13234
  __messaging_thread_id: input2.metadata.threadId
13241
13235
  } : {},
13242
13236
  __activeConversationId: conversationId
13243
- }, latestConversation ?? { _toolResultArchive: {} })
13237
+ }, { _toolResultArchive: {} })
13244
13238
  };
13245
13239
  try {
13246
13240
  const execution = await executeConversationTurn2({
@@ -15270,12 +15264,7 @@ data: ${JSON.stringify(frame)}
15270
15264
  task: messageText,
15271
15265
  conversationId,
15272
15266
  tenantId: ctx.tenantId ?? void 0,
15273
- parameters: withToolResultArchiveParam({
15274
- ...bodyParameters ?? {},
15275
- ...buildRecallParams({ ownerId, tenantId: ctx.tenantId, excludeConversationId: conversationId }),
15276
- __activeConversationId: conversationId,
15277
- __ownerId: ownerId
15278
- }, conversation),
15267
+ parameters: buildTurnParameters(conversation, { bodyParameters }),
15279
15268
  messages: harnessMessages,
15280
15269
  files: files.length > 0 ? files : void 0,
15281
15270
  abortSignal: abortController.signal
@@ -15535,7 +15524,9 @@ ${cronJob.task}`;
15535
15524
  conv._toolResultArchive,
15536
15525
  async (event) => {
15537
15526
  await telemetry.emit(event);
15538
- }
15527
+ },
15528
+ buildTurnParameters(conv),
15529
+ conv.tenantId
15539
15530
  );
15540
15531
  const freshConv = await conversationStore.get(conv.conversationId);
15541
15532
  if (freshConv) {
@@ -15603,7 +15594,9 @@ ${cronJob.task}`;
15603
15594
  async (event) => {
15604
15595
  broadcastEvent(convId, event);
15605
15596
  await telemetry.emit(event);
15606
- }
15597
+ },
15598
+ buildTurnParameters(conversation),
15599
+ conversation.tenantId
15607
15600
  );
15608
15601
  finishConversationStream(convId);
15609
15602
  const freshConv = await conversationStore.get(convId);
@@ -15721,7 +15714,10 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
15721
15714
  framedMessage,
15722
15715
  originConv.conversationId,
15723
15716
  originConv.messages ?? [],
15724
- originConv._toolResultArchive
15717
+ originConv._toolResultArchive,
15718
+ void 0,
15719
+ buildTurnParameters(originConv),
15720
+ originConv.tenantId
15725
15721
  );
15726
15722
  if (result.response) {
15727
15723
  try {
@@ -15754,7 +15750,16 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
15754
15750
  `[reminder] ${reminder.task.slice(0, 80)} ${timestamp}`
15755
15751
  );
15756
15752
  const convId = conversation.conversationId;
15757
- const result = await runCronAgent(harness, framedMessage, convId, []);
15753
+ const result = await runCronAgent(
15754
+ harness,
15755
+ framedMessage,
15756
+ convId,
15757
+ [],
15758
+ void 0,
15759
+ void 0,
15760
+ buildTurnParameters(conversation),
15761
+ conversation.tenantId
15762
+ );
15758
15763
  const freshConv = await conversationStore.get(convId);
15759
15764
  if (freshConv) {
15760
15765
  freshConv.messages = buildCronMessages(framedMessage, [], result);
@@ -15787,6 +15792,7 @@ Scheduled for: ${new Date(reminder.scheduledAt).toISOString()}` + recurrenceNote
15787
15792
  handler._finishConversationStream = finishConversationStream;
15788
15793
  handler._checkAndFireReminders = checkAndFireReminders;
15789
15794
  handler._reminderPollIntervalMs = reminderPollWindowMs;
15795
+ handler._buildTurnParameters = buildTurnParameters;
15790
15796
  orchestrator.recoverStaleSubagents().catch(
15791
15797
  (err) => subagentLog.warn(`failed to recover stale subagent runs: ${formatError(err)}`)
15792
15798
  );
@@ -15817,6 +15823,7 @@ var startDevServer = async (port, options) => {
15817
15823
  const activeRuns = handler._activeConversationRuns;
15818
15824
  const deferredCallbacks = handler._pendingCallbackNeeded;
15819
15825
  const runCallback = handler._processSubagentCallback;
15826
+ const buildParams = handler._buildTurnParameters;
15820
15827
  if (!harnessRef || !store) return;
15821
15828
  for (const [jobName, config] of entries) {
15822
15829
  const job = new Cron(
@@ -15873,7 +15880,9 @@ ${config.task}`;
15873
15880
  convId,
15874
15881
  historyMessages,
15875
15882
  conversation._toolResultArchive,
15876
- broadcastCh ? (ev) => broadcastCh(convId, ev) : void 0
15883
+ broadcastCh ? (ev) => broadcastCh(convId, ev) : void 0,
15884
+ buildParams?.(conversation),
15885
+ conversation.tenantId
15877
15886
  );
15878
15887
  handler._finishConversationStream?.(convId);
15879
15888
  const freshConv = await store.get(convId);
@@ -15941,7 +15950,9 @@ ${config.task}`;
15941
15950
  cronConvId,
15942
15951
  [],
15943
15952
  conversation._toolResultArchive,
15944
- broadcast ? (ev) => broadcast(cronConvId, ev) : void 0
15953
+ broadcast ? (ev) => broadcast(cronConvId, ev) : void 0,
15954
+ buildParams?.(conversation),
15955
+ conversation.tenantId
15945
15956
  );
15946
15957
  handler._finishConversationStream?.(cronConvId);
15947
15958
  const freshConv = await store.get(cronConvId);
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  main
4
- } from "./chunk-U643TWFX.js";
4
+ } from "./chunk-W7SQVUB4.js";
5
5
 
6
6
  // src/cli.ts
7
7
  void main();
package/dist/index.d.ts CHANGED
@@ -46,7 +46,7 @@ type CronRunResult = {
46
46
  /** Timestamp shared by user and assistant messages of this turn. */
47
47
  turnTimestamp: number;
48
48
  };
49
- declare const runCronAgent: (harnessRef: AgentHarness, task: string, conversationId: string, historyMessages: Message[], toolResultArchive?: Conversation["_toolResultArchive"], onEvent?: (event: AgentEvent) => void | Promise<void>) => Promise<CronRunResult>;
49
+ declare const runCronAgent: (harnessRef: AgentHarness, task: string, conversationId: string, historyMessages: Message[], toolResultArchive?: Conversation["_toolResultArchive"], onEvent?: (event: AgentEvent) => void | Promise<void>, parameters?: Record<string, unknown>, tenantId?: string | null) => Promise<CronRunResult>;
50
50
  declare const buildCronMessages: (task: string, historyMessages: Message[], result: CronRunResult) => Message[];
51
51
  /** Append a cron turn to a freshly-fetched conversation (avoids overwriting concurrent writes). */
52
52
  declare const appendCronTurn: (conv: Conversation, task: string, result: CronRunResult) => void;
@@ -224,6 +224,17 @@ type RequestHandler = ((request: IncomingMessage, response: ServerResponse) => P
224
224
  duration: number;
225
225
  }>;
226
226
  _reminderPollIntervalMs?: number;
227
+ _buildTurnParameters?: (conversation: Conversation, opts?: {
228
+ bodyParameters?: Record<string, unknown>;
229
+ messagingMetadata?: {
230
+ platform: string;
231
+ sender: {
232
+ id: string;
233
+ name?: string | null;
234
+ };
235
+ threadId?: string;
236
+ };
237
+ }) => Record<string, unknown>;
227
238
  };
228
239
  declare const createRequestHandler: (options?: {
229
240
  workingDir?: string;
package/dist/index.js CHANGED
@@ -77,7 +77,7 @@ import {
77
77
  writeHtml,
78
78
  writeJson,
79
79
  writeScaffoldFile
80
- } from "./chunk-U643TWFX.js";
80
+ } from "./chunk-W7SQVUB4.js";
81
81
  export {
82
82
  AGENT_TEMPLATE,
83
83
  ENV_TEMPLATE,
@@ -3,7 +3,7 @@ import {
3
3
  getMascotLines,
4
4
  inferConversationTitle,
5
5
  resolveHarnessEnvironment
6
- } from "./chunk-U643TWFX.js";
6
+ } from "./chunk-W7SQVUB4.js";
7
7
 
8
8
  // src/run-interactive-ink.ts
9
9
  import * as readline from "readline";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@poncho-ai/cli",
3
- "version": "0.38.0",
3
+ "version": "0.38.1",
4
4
  "description": "CLI for building and deploying AI agents",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,7 +28,7 @@
28
28
  "react": "^19.2.4",
29
29
  "react-devtools-core": "^6.1.5",
30
30
  "yaml": "^2.8.1",
31
- "@poncho-ai/harness": "0.39.0",
31
+ "@poncho-ai/harness": "0.39.1",
32
32
  "@poncho-ai/messaging": "0.8.4",
33
33
  "@poncho-ai/sdk": "1.9.0"
34
34
  },
@@ -76,19 +76,28 @@ export const runCronAgent = async (
76
76
  historyMessages: Message[],
77
77
  toolResultArchive?: Conversation["_toolResultArchive"],
78
78
  onEvent?: (event: AgentEvent) => void | Promise<void>,
79
+ parameters?: Record<string, unknown>,
80
+ tenantId?: string | null,
79
81
  ): Promise<CronRunResult> => {
80
82
  const turnTimestamp = Date.now();
81
83
  const userMessageId = randomUUID();
82
84
  const assistantId = randomUUID();
85
+ // Callers normally build `parameters` via buildTurnParameters() which
86
+ // already merges the tool-result archive. The `toolResultArchive` arg is a
87
+ // fallback for callers that don't (legacy / minimal callers).
88
+ const finalParameters = {
89
+ ...(parameters ?? {}),
90
+ __activeConversationId: conversationId,
91
+ [TOOL_RESULT_ARCHIVE_PARAM]:
92
+ parameters?.[TOOL_RESULT_ARCHIVE_PARAM] ?? toolResultArchive ?? {},
93
+ };
83
94
  const execution = await executeConversationTurn({
84
95
  harness: harnessRef,
85
96
  runInput: {
86
97
  task,
87
98
  conversationId,
88
- parameters: {
89
- __activeConversationId: conversationId,
90
- [TOOL_RESULT_ARCHIVE_PARAM]: toolResultArchive ?? {},
91
- },
99
+ tenantId: tenantId ?? undefined,
100
+ parameters: finalParameters,
92
101
  messages: historyMessages,
93
102
  },
94
103
  onEvent,
package/src/index.ts CHANGED
@@ -307,6 +307,13 @@ export type RequestHandler = ((
307
307
  _finishConversationStream?: (conversationId: string) => void;
308
308
  _checkAndFireReminders?: () => Promise<{ fired: string[]; count: number; duration: number }>;
309
309
  _reminderPollIntervalMs?: number;
310
+ _buildTurnParameters?: (
311
+ conversation: Conversation,
312
+ opts?: {
313
+ bodyParameters?: Record<string, unknown>;
314
+ messagingMetadata?: { platform: string; sender: { id: string; name?: string | null }; threadId?: string };
315
+ },
316
+ ) => Record<string, unknown>;
310
317
  };
311
318
 
312
319
  export const createRequestHandler = async (options?: {
@@ -681,6 +688,40 @@ export const createRequestHandler = async (options?: {
681
688
  };
682
689
  };
683
690
 
691
+ // ---------------------------------------------------------------------------
692
+ // Single helper for assembling runInput.parameters across every turn entry
693
+ // point (HTTP route, messaging adapter, cron, reminder). All `__`-prefixed
694
+ // context params live here so adding a new one only requires one edit.
695
+ // ---------------------------------------------------------------------------
696
+ const buildTurnParameters = (
697
+ conversation: Conversation,
698
+ opts: {
699
+ bodyParameters?: Record<string, unknown>;
700
+ messagingMetadata?: {
701
+ platform: string;
702
+ sender: { id: string; name?: string | null };
703
+ threadId?: string;
704
+ };
705
+ } = {},
706
+ ): Record<string, unknown> => {
707
+ return withToolResultArchiveParam({
708
+ ...(opts.bodyParameters ?? {}),
709
+ ...buildRecallParams({
710
+ ownerId: conversation.ownerId,
711
+ tenantId: conversation.tenantId,
712
+ excludeConversationId: conversation.conversationId,
713
+ }),
714
+ ...(opts.messagingMetadata ? {
715
+ __messaging_platform: opts.messagingMetadata.platform,
716
+ __messaging_sender_id: opts.messagingMetadata.sender.id,
717
+ __messaging_sender_name: opts.messagingMetadata.sender.name ?? "",
718
+ __messaging_thread_id: opts.messagingMetadata.threadId,
719
+ } : {}),
720
+ __activeConversationId: conversation.conversationId,
721
+ __ownerId: conversation.ownerId,
722
+ }, conversation);
723
+ };
724
+
684
725
  // Subagent lifecycle extracted to AgentOrchestrator (Phase 5).
685
726
 
686
727
  // ---------------------------------------------------------------------------
@@ -828,17 +869,22 @@ export const createRequestHandler = async (options?: {
828
869
  const runInput = {
829
870
  task: input.task,
830
871
  conversationId,
872
+ tenantId: latestConversation?.tenantId ?? undefined,
831
873
  messages: historyMessages,
832
874
  files: input.files,
833
- parameters: withToolResultArchiveParam({
834
- ...(input.metadata ? {
835
- __messaging_platform: input.metadata.platform,
836
- __messaging_sender_id: input.metadata.sender.id,
837
- __messaging_sender_name: input.metadata.sender.name ?? "",
838
- __messaging_thread_id: input.metadata.threadId,
839
- } : {}),
840
- __activeConversationId: conversationId,
841
- }, latestConversation ?? { _toolResultArchive: {} } as Conversation),
875
+ parameters: latestConversation
876
+ ? buildTurnParameters(latestConversation, {
877
+ messagingMetadata: input.metadata,
878
+ })
879
+ : withToolResultArchiveParam({
880
+ ...(input.metadata ? {
881
+ __messaging_platform: input.metadata.platform,
882
+ __messaging_sender_id: input.metadata.sender.id,
883
+ __messaging_sender_name: input.metadata.sender.name ?? "",
884
+ __messaging_thread_id: input.metadata.threadId,
885
+ } : {}),
886
+ __activeConversationId: conversationId,
887
+ }, { _toolResultArchive: {} } as Conversation),
842
888
  };
843
889
 
844
890
  try {
@@ -3170,12 +3216,7 @@ export const createRequestHandler = async (options?: {
3170
3216
  task: messageText,
3171
3217
  conversationId,
3172
3218
  tenantId: ctx.tenantId ?? undefined,
3173
- parameters: withToolResultArchiveParam({
3174
- ...(bodyParameters ?? {}),
3175
- ...buildRecallParams({ ownerId, tenantId: ctx.tenantId, excludeConversationId: conversationId }),
3176
- __activeConversationId: conversationId,
3177
- __ownerId: ownerId,
3178
- }, conversation),
3219
+ parameters: buildTurnParameters(conversation, { bodyParameters }),
3179
3220
  messages: harnessMessages,
3180
3221
  files: files.length > 0 ? files : undefined,
3181
3222
  abortSignal: abortController.signal,
@@ -3456,6 +3497,8 @@ export const createRequestHandler = async (options?: {
3456
3497
  const result = await runCronAgent(harness, task, conv.conversationId, historyMessages,
3457
3498
  conv._toolResultArchive,
3458
3499
  async (event) => { await telemetry.emit(event); },
3500
+ buildTurnParameters(conv),
3501
+ conv.tenantId,
3459
3502
  );
3460
3503
 
3461
3504
  const freshConv = await conversationStore.get(conv.conversationId);
@@ -3526,6 +3569,8 @@ export const createRequestHandler = async (options?: {
3526
3569
  broadcastEvent(convId, event);
3527
3570
  await telemetry.emit(event);
3528
3571
  },
3572
+ buildTurnParameters(conversation),
3573
+ conversation.tenantId,
3529
3574
  );
3530
3575
  finishConversationStream(convId);
3531
3576
 
@@ -3673,6 +3718,9 @@ export const createRequestHandler = async (options?: {
3673
3718
  harness, framedMessage, originConv.conversationId,
3674
3719
  originConv.messages ?? [],
3675
3720
  originConv._toolResultArchive,
3721
+ undefined,
3722
+ buildTurnParameters(originConv),
3723
+ originConv.tenantId,
3676
3724
  );
3677
3725
  if (result.response) {
3678
3726
  try {
@@ -3705,7 +3753,13 @@ export const createRequestHandler = async (options?: {
3705
3753
  `[reminder] ${reminder.task.slice(0, 80)} ${timestamp}`,
3706
3754
  );
3707
3755
  const convId = conversation.conversationId;
3708
- const result = await runCronAgent(harness, framedMessage, convId, []);
3756
+ const result = await runCronAgent(
3757
+ harness, framedMessage, convId, [],
3758
+ undefined,
3759
+ undefined,
3760
+ buildTurnParameters(conversation),
3761
+ conversation.tenantId,
3762
+ );
3709
3763
  const freshConv = await conversationStore.get(convId);
3710
3764
  if (freshConv) {
3711
3765
  freshConv.messages = buildCronMessages(framedMessage, [], result);
@@ -3741,6 +3795,7 @@ export const createRequestHandler = async (options?: {
3741
3795
  handler._finishConversationStream = finishConversationStream;
3742
3796
  handler._checkAndFireReminders = checkAndFireReminders;
3743
3797
  handler._reminderPollIntervalMs = reminderPollWindowMs;
3798
+ handler._buildTurnParameters = buildTurnParameters;
3744
3799
 
3745
3800
  // Recover stale subagent runs that were "running" when the server last stopped
3746
3801
  orchestrator.recoverStaleSubagents().catch(err =>
@@ -3785,6 +3840,7 @@ export const startDevServer = async (
3785
3840
  const activeRuns = handler._activeConversationRuns;
3786
3841
  const deferredCallbacks = handler._pendingCallbackNeeded;
3787
3842
  const runCallback = handler._processSubagentCallback;
3843
+ const buildParams = handler._buildTurnParameters;
3788
3844
  if (!harnessRef || !store) return;
3789
3845
 
3790
3846
  for (const [jobName, config] of entries) {
@@ -3845,6 +3901,8 @@ export const startDevServer = async (
3845
3901
  const result = await runCronAgent(harnessRef, task, convId, historyMessages,
3846
3902
  conversation._toolResultArchive,
3847
3903
  broadcastCh ? (ev) => broadcastCh(convId, ev) : undefined,
3904
+ buildParams?.(conversation),
3905
+ conversation.tenantId,
3848
3906
  );
3849
3907
  handler._finishConversationStream?.(convId);
3850
3908
 
@@ -3913,6 +3971,8 @@ export const startDevServer = async (
3913
3971
  const result = await runCronAgent(harnessRef, config.task, cronConvId, [],
3914
3972
  conversation._toolResultArchive,
3915
3973
  broadcast ? (ev) => broadcast(cronConvId!, ev) : undefined,
3974
+ buildParams?.(conversation),
3975
+ conversation.tenantId,
3916
3976
  );
3917
3977
  handler._finishConversationStream?.(cronConvId);
3918
3978
  const freshConv = await store.get(cronConvId);
@@ -59,7 +59,6 @@ export const getWebUiClientScript = (markedSource: string): string => `
59
59
  open: false,
60
60
  threadId: null,
61
61
  parentMessageId: null,
62
- parentMessage: null,
63
62
  messages: [],
64
63
  isStreaming: false,
65
64
  abortController: null,
@@ -106,7 +105,6 @@ export const getWebUiClientScript = (markedSource: string): string => `
106
105
  threadPanel: $("thread-panel"),
107
106
  threadPanelResize: $("thread-panel-resize"),
108
107
  threadPanelClose: $("thread-panel-close"),
109
- threadPanelParent: $("thread-panel-parent"),
110
108
  threadPanelMessages: $("thread-panel-messages"),
111
109
  threadComposer: $("thread-composer"),
112
110
  threadAttachBtn: $("thread-attach-btn"),
@@ -1437,24 +1435,6 @@ export const getWebUiClientScript = (markedSource: string): string => `
1437
1435
  root.scrollTop = root.scrollHeight;
1438
1436
  };
1439
1437
 
1440
- const renderThreadPanelParent = () => {
1441
- const root = elements.threadPanelParent;
1442
- if (!root) return;
1443
- root.innerHTML = "";
1444
- const parent = state.threadPanel.parentMessage;
1445
- if (!parent) {
1446
- const empty = document.createElement("div");
1447
- empty.className = "thread-panel-parent-empty";
1448
- empty.textContent = "No parent context";
1449
- root.appendChild(empty);
1450
- return;
1451
- }
1452
- const col = document.createElement("div");
1453
- col.className = "messages-column";
1454
- col.appendChild(buildSimpleMessageRow(parent));
1455
- root.appendChild(col);
1456
- };
1457
-
1458
1438
  const closeThreadPanel = () => {
1459
1439
  if (state.threadPanel.abortController) {
1460
1440
  try { state.threadPanel.abortController.abort(); } catch (e) {}
@@ -1462,7 +1442,6 @@ export const getWebUiClientScript = (markedSource: string): string => `
1462
1442
  state.threadPanel.open = false;
1463
1443
  state.threadPanel.threadId = null;
1464
1444
  state.threadPanel.parentMessageId = null;
1465
- state.threadPanel.parentMessage = null;
1466
1445
  state.threadPanel.messages = [];
1467
1446
  state.threadPanel.isStreaming = false;
1468
1447
  state.threadPanel.abortController = null;
@@ -1491,14 +1470,15 @@ export const getWebUiClientScript = (markedSource: string): string => `
1491
1470
  const renderActiveTopForThreadPanel = (payload) => {
1492
1471
  const conv = payload.conversation || {};
1493
1472
  const allMsgs = Array.isArray(conv.messages) ? conv.messages : [];
1473
+ // Show the anchor message + replies. The earlier snapshot is still
1474
+ // part of the thread's context server-side, but the panel only
1475
+ // displays what's relevant: the message you forked on, plus what
1476
+ // came after.
1494
1477
  const snapshotLength = (conv.threadMeta && typeof conv.threadMeta.snapshotLength === "number")
1495
1478
  ? conv.threadMeta.snapshotLength
1496
1479
  : allMsgs.length;
1497
- const parent = allMsgs[snapshotLength - 1] || null;
1498
- const replies = allMsgs.slice(snapshotLength);
1499
- state.threadPanel.parentMessage = parent;
1500
- state.threadPanel.messages = replies;
1501
- renderThreadPanelParent();
1480
+ const startIdx = Math.max(0, snapshotLength - 1);
1481
+ state.threadPanel.messages = allMsgs.slice(startIdx);
1502
1482
  renderThreadPanelMessages();
1503
1483
  };
1504
1484
 
@@ -2227,21 +2227,12 @@ export const WEB_UI_STYLES = `
2227
2227
  border-radius: 4px;
2228
2228
  }
2229
2229
  .thread-panel-close:hover { color: var(--fg); }
2230
- .thread-panel-parent {
2231
- padding: 12px 16px;
2232
- border-bottom: 1px solid var(--border);
2233
- background: var(--bg-bubble-user, rgba(0,0,0,0.02));
2234
- font-size: 13px;
2235
- }
2236
- .thread-panel-parent .message-row {
2237
- margin: 0;
2238
- }
2239
- .thread-panel-parent-empty {
2240
- color: var(--fg-3);
2241
- font-style: italic;
2242
- }
2243
2230
  .thread-panel-messages {
2244
- flex: 1;
2231
+ flex: 1 1 0;
2232
+ /* min-height: 0 is required so this flex item can shrink below its
2233
+ content size — without it, overflow-y:auto never engages because
2234
+ the element grows to fit all messages instead of scrolling. */
2235
+ min-height: 0;
2245
2236
  overflow-y: auto;
2246
2237
  padding: 12px 16px;
2247
2238
  }
package/src/web-ui.ts CHANGED
@@ -211,7 +211,6 @@ ${WEB_UI_STYLES}
211
211
  <span class="thread-panel-title">Thread</span>
212
212
  <button id="thread-panel-close" class="thread-panel-close" title="Close thread">&times;</button>
213
213
  </div>
214
- <div id="thread-panel-parent" class="thread-panel-parent"></div>
215
214
  <div id="thread-panel-messages" class="thread-panel-messages messages"></div>
216
215
  <form id="thread-composer" class="composer thread-composer">
217
216
  <div class="composer-inner">