pi-ui-extend 0.1.46 → 0.1.47

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.
@@ -107,6 +107,7 @@ export declare class AppTabsController {
107
107
  private observeRuntimeForTab;
108
108
  private shouldSyncTabFromRuntimeEvent;
109
109
  private shouldScheduleDelayedSyncForRuntimeEvent;
110
+ private shouldInvalidateCachedViewForRuntimeEvent;
110
111
  private scheduleDelayedRuntimeSync;
111
112
  private clearRuntimeRefreshTimers;
112
113
  private clearHistoryReloadTimers;
@@ -641,8 +641,7 @@ export class AppTabsController {
641
641
  void this.saveTabs();
642
642
  this.scheduleTabPrewarm();
643
643
  const cachedView = this.sessionViewsByTabId.get(target.id);
644
- const cachedViewNeedsHistoryReload = this.tabIdsNeedingHistoryReload.has(target.id)
645
- && this.sessionActivity(targetRuntime.session) !== "running";
644
+ const cachedViewNeedsHistoryReload = this.tabIdsNeedingHistoryReload.has(target.id);
646
645
  if (cachedView && this.host.restoreSessionView && !cachedViewNeedsHistoryReload) {
647
646
  this.host.restoreSessionView(cachedView);
648
647
  this.restoreDeferredUserMessages(target.id);
@@ -883,9 +882,11 @@ export class AppTabsController {
883
882
  return;
884
883
  existing?.unsubscribe();
885
884
  const unsubscribe = runtime.session.subscribe((event) => {
885
+ if (this.shouldInvalidateCachedViewForRuntimeEvent(event)) {
886
+ this.tabIdsNeedingHistoryReload.add(tabId);
887
+ }
886
888
  if (this.shouldScheduleDelayedSyncForRuntimeEvent(event)) {
887
889
  this.scheduleDelayedRuntimeSync(tabId, runtime);
888
- this.tabIdsNeedingHistoryReload.add(tabId);
889
890
  }
890
891
  if (!this.shouldSyncTabFromRuntimeEvent(event))
891
892
  return;
@@ -905,6 +906,17 @@ export class AppTabsController {
905
906
  || event.type === "turn_end"
906
907
  || event.type === "compaction_end";
907
908
  }
909
+ shouldInvalidateCachedViewForRuntimeEvent(event) {
910
+ return event.type === "message_start"
911
+ || event.type === "message_update"
912
+ || event.type === "message_end"
913
+ || event.type === "tool_execution_start"
914
+ || event.type === "tool_execution_update"
915
+ || event.type === "tool_execution_end"
916
+ || event.type === "agent_end"
917
+ || event.type === "turn_end"
918
+ || event.type === "compaction_end";
919
+ }
908
920
  scheduleDelayedRuntimeSync(tabId, runtime) {
909
921
  this.clearRuntimeRefreshTimers(tabId);
910
922
  for (const delayMs of [0, 100, 500, 1500, 3000]) {
@@ -123,12 +123,28 @@ function appendTextToContent(content: unknown, text: string): unknown {
123
123
 
124
124
  function appendDcpControlToMessages(messages: unknown, text: string): unknown {
125
125
  if (!Array.isArray(messages)) return messages
126
- const existingIndex = messages.findIndex((message: any) =>
127
- message?.role === "system" || message?.role === "developer"
128
- )
129
126
  const block = `${DCP_PROVIDER_CONTROL_HEADER}\n${text}`
130
- if (existingIndex >= 0) {
131
- return messages.map((message: any, index) => index === existingIndex
127
+
128
+ // Keep DCP's volatile ID map at the tail of the provider context instead of
129
+ // mutating the stable system/developer prefix. Prefix cache reuse is much
130
+ // better when only the newest transcript item changes on each request.
131
+ let targetIndex = -1
132
+ for (let index = messages.length - 1; index >= 0; index--) {
133
+ const message = messages[index] as any
134
+ if (!message || typeof message !== "object") continue
135
+ if (message.role === "system" || message.role === "developer") continue
136
+ targetIndex = index
137
+ break
138
+ }
139
+
140
+ if (targetIndex < 0) {
141
+ targetIndex = messages.findIndex((message: any) =>
142
+ message?.role === "system" || message?.role === "developer"
143
+ )
144
+ }
145
+
146
+ if (targetIndex >= 0) {
147
+ return messages.map((message: any, index) => index === targetIndex
132
148
  ? { ...message, content: appendTextToContent(message.content, block) }
133
149
  : message)
134
150
  }
@@ -155,10 +171,6 @@ function appendDcpControlToProviderPayload(payload: unknown, text: string): unkn
155
171
  if (!payload || typeof payload !== "object") return payload
156
172
  const record = payload as Record<string, unknown>
157
173
 
158
- if ("system" in record) {
159
- return { ...record, system: appendDcpControlToAnthropicSystem(record.system, text) }
160
- }
161
-
162
174
  if (Array.isArray(record.input)) {
163
175
  return { ...record, input: appendDcpControlToMessages(record.input, text) }
164
176
  }
@@ -167,6 +179,10 @@ function appendDcpControlToProviderPayload(payload: unknown, text: string): unkn
167
179
  return { ...record, messages: appendDcpControlToMessages(record.messages, text) }
168
180
  }
169
181
 
182
+ if ("system" in record) {
183
+ return { ...record, system: appendDcpControlToAnthropicSystem(record.system, text) }
184
+ }
185
+
170
186
  if (record.config && typeof record.config === "object") {
171
187
  const config = record.config as Record<string, unknown>
172
188
  return {
@@ -46,27 +46,87 @@ function priorityForMessage(tokenEstimate: number, config: DcpConfig | undefined
46
46
  return "low";
47
47
  }
48
48
 
49
- function controlLineForMessageId(id: string, state: DcpState): string {
50
- const meta = state.messageMetaSnapshot.get(id);
51
- if (!meta) return `- ${id}`;
52
-
53
- const details: string[] = [meta.role];
54
- if (meta.blockId !== undefined) details.push(`block=b${meta.blockId}`);
55
- if (meta.toolName) details.push(`tool=${meta.toolName}`);
56
- if (meta.priority) details.push(`priority=${meta.priority}`);
57
- return `- ${id}: ${details.join(", ")}`;
49
+ function messageIdIndex(id: string): number | undefined {
50
+ const match = /^m(\d+)$/.exec(id);
51
+ if (!match) return undefined;
52
+ const index = Number.parseInt(match[1]!, 10);
53
+ return Number.isFinite(index) ? index : undefined;
54
+ }
55
+
56
+ function summarizeMessageIds(ids: string[]): string {
57
+ if (ids.length === 0) return "none";
58
+
59
+ const ranges: string[] = [];
60
+ let start = ids[0]!;
61
+ let previous = ids[0]!;
62
+ let previousIndex = messageIdIndex(previous);
63
+
64
+ const flush = () => {
65
+ const startIndex = messageIdIndex(start);
66
+ const endIndex = messageIdIndex(previous);
67
+ if (
68
+ startIndex !== undefined &&
69
+ endIndex !== undefined &&
70
+ endIndex - startIndex >= 2
71
+ ) {
72
+ ranges.push(`${start}..${previous}`);
73
+ } else if (start === previous) {
74
+ ranges.push(start);
75
+ } else {
76
+ ranges.push(start, previous);
77
+ }
78
+ };
79
+
80
+ for (let i = 1; i < ids.length; i++) {
81
+ const id = ids[i]!;
82
+ const index = messageIdIndex(id);
83
+ if (previousIndex !== undefined && index === previousIndex + 1) {
84
+ previous = id;
85
+ previousIndex = index;
86
+ continue;
87
+ }
88
+
89
+ flush();
90
+ start = id;
91
+ previous = id;
92
+ previousIndex = index;
93
+ }
94
+
95
+ flush();
96
+ return ranges.join(", ");
97
+ }
98
+
99
+ function compactPriorityHint(ids: string[], state: DcpState): string | undefined {
100
+ const high: string[] = [];
101
+ const medium: string[] = [];
102
+ const blocks: string[] = [];
103
+
104
+ for (const id of ids) {
105
+ const meta = state.messageMetaSnapshot.get(id);
106
+ if (!meta) continue;
107
+ if (meta.priority === "high") high.push(id);
108
+ else if (meta.priority === "medium") medium.push(id);
109
+ if (meta.blockId !== undefined) blocks.push(`${id}=b${meta.blockId}`);
110
+ }
111
+
112
+ const parts: string[] = [];
113
+ if (high.length > 0) parts.push(`high=${summarizeMessageIds(high)}`);
114
+ if (medium.length > 0) parts.push(`medium=${summarizeMessageIds(medium)}`);
115
+ if (blocks.length > 0) parts.push(`blocks=${blocks.join(",")}`);
116
+ return parts.length > 0 ? `Hints: ${parts.join("; ")}` : undefined;
58
117
  }
59
118
 
60
119
  export function buildMessageIdControlText(state: DcpState): string | undefined {
61
120
  const ids = [...state.messageIdSnapshot.keys()];
62
121
  if (ids.length === 0) return undefined;
63
122
 
123
+ const hint = compactPriorityHint(ids, state);
124
+
64
125
  return [
65
126
  "<dcp-message-ids>",
66
- "DCP metadata for the preceding conversation messages. These IDs are model-visible but UI-hidden control data.",
67
- "Use only these current IDs with the compress tool; do not quote or output this metadata.",
68
- `Current raw message IDs: ${ids.join(", ")}`,
69
- ...ids.map((id) => controlLineForMessageId(id, state)),
127
+ "DCP metadata for the preceding conversation messages. IDs follow current message order; use only these IDs with compress; do not quote/output.",
128
+ `Current raw message IDs: ${summarizeMessageIds(ids)}${ids.length > 2 ? ` (${ids.length} messages)` : ""}`,
129
+ ...(hint ? [hint] : []),
70
130
  "</dcp-message-ids>",
71
131
  ].join("\n");
72
132
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extend",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {