pi-ui-extend 0.1.34 → 0.1.36
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/README.md +20 -0
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +12 -2
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +32 -0
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-session-actions.js +2 -0
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +4 -0
- package/dist/app/input/input-controller.d.ts +5 -1
- package/dist/app/input/input-controller.js +122 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +21 -0
- package/dist/app/input/terminal-edit-shortcuts.js +92 -16
- package/dist/app/popup/popup-action-controller.d.ts +1 -0
- package/dist/app/popup/popup-action-controller.js +1 -0
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +7 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +4 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +1 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +5 -5
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
|
@@ -26,6 +26,8 @@ export class AppSessionEventController {
|
|
|
26
26
|
historyEntries = [];
|
|
27
27
|
historyWindowStart = 0;
|
|
28
28
|
currentThinkingEntryId;
|
|
29
|
+
currentThinkingEntryStartedAt;
|
|
30
|
+
thinkingElapsedRenderTimer;
|
|
29
31
|
assistantMessageClosed = false;
|
|
30
32
|
assistantTextBuffer = "";
|
|
31
33
|
constructor(host) {
|
|
@@ -44,6 +46,7 @@ export class AppSessionEventController {
|
|
|
44
46
|
currentAssistantTextBlockContentIndex: this.currentAssistantTextBlockContentIndex,
|
|
45
47
|
assistantTextBlocksByContentIndex: new Map(this.assistantTextBlocksByContentIndex),
|
|
46
48
|
currentThinkingEntryId: this.currentThinkingEntryId,
|
|
49
|
+
currentThinkingEntryStartedAt: this.currentThinkingEntryStartedAt,
|
|
47
50
|
assistantMessageClosed: this.assistantMessageClosed,
|
|
48
51
|
assistantTextBuffer: this.assistantTextBuffer,
|
|
49
52
|
entryRenderVersions: new Map(this.entryRenderVersions),
|
|
@@ -71,6 +74,8 @@ export class AppSessionEventController {
|
|
|
71
74
|
for (const [key, value] of state.assistantTextBlocksByContentIndex)
|
|
72
75
|
this.assistantTextBlocksByContentIndex.set(key, value);
|
|
73
76
|
this.currentThinkingEntryId = state.currentThinkingEntryId;
|
|
77
|
+
this.currentThinkingEntryStartedAt = state.currentThinkingEntryStartedAt;
|
|
78
|
+
this.syncThinkingElapsedRenderTimer();
|
|
74
79
|
this.assistantMessageClosed = state.assistantMessageClosed;
|
|
75
80
|
this.assistantTextBuffer = state.assistantTextBuffer;
|
|
76
81
|
this.entryRenderVersions.clear();
|
|
@@ -94,6 +99,8 @@ export class AppSessionEventController {
|
|
|
94
99
|
this.assistantTextBlocksByContentIndex.clear();
|
|
95
100
|
this.finalizedToolCallContentIndexes.clear();
|
|
96
101
|
this.currentThinkingEntryId = undefined;
|
|
102
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
103
|
+
this.stopThinkingElapsedRenderTimer();
|
|
97
104
|
this.assistantMessageClosed = false;
|
|
98
105
|
this.assistantTextBuffer = "";
|
|
99
106
|
this.olderHistoryLoader = undefined;
|
|
@@ -563,6 +570,7 @@ export class AppSessionEventController {
|
|
|
563
570
|
this.handleToolCallStreamUpdate(assistantEvent.contentIndex, assistantEvent.partial, assistantEvent.toolCall);
|
|
564
571
|
break;
|
|
565
572
|
case "done":
|
|
573
|
+
this.reconcileAssistantTextFromFinalMessage(assistantEvent.message);
|
|
566
574
|
this.renderAssistantToolCallsFromMessage(assistantEvent.message);
|
|
567
575
|
this.finishCurrentThinkingEntry();
|
|
568
576
|
this.flushAssistantTextBuffer(true);
|
|
@@ -754,12 +762,15 @@ export class AppSessionEventController {
|
|
|
754
762
|
: undefined;
|
|
755
763
|
if (!entry || entry.kind !== "thinking") {
|
|
756
764
|
const level = this.currentThinkingLevel();
|
|
765
|
+
const startedAt = this.currentThinkingEntryStartedAt ?? Date.now();
|
|
766
|
+
this.currentThinkingEntryStartedAt = startedAt;
|
|
757
767
|
entry = {
|
|
758
768
|
id: createId("thinking"),
|
|
759
769
|
kind: "thinking",
|
|
760
770
|
text: "",
|
|
761
771
|
expanded: this.host.toolDefaultExpanded(THINKING_TOOL_NAME),
|
|
762
772
|
...(level === undefined ? {} : { level }),
|
|
773
|
+
startedAt,
|
|
763
774
|
status: "running",
|
|
764
775
|
};
|
|
765
776
|
this.addEntry(entry);
|
|
@@ -770,17 +781,24 @@ export class AppSessionEventController {
|
|
|
770
781
|
delete entry.level;
|
|
771
782
|
else
|
|
772
783
|
entry.level = level;
|
|
784
|
+
entry.startedAt ??= this.currentThinkingEntryStartedAt ?? Date.now();
|
|
785
|
+
this.currentThinkingEntryStartedAt = entry.startedAt;
|
|
786
|
+
delete entry.finishedAt;
|
|
773
787
|
entry.status = "running";
|
|
774
788
|
entry.text += delta;
|
|
789
|
+
this.startThinkingElapsedRenderTimer();
|
|
775
790
|
this.touchEntry(entry);
|
|
776
791
|
}
|
|
777
792
|
finishCurrentThinkingEntry() {
|
|
778
793
|
const entry = this.currentThinkingEntryId ? this.findEntry(this.currentThinkingEntryId) : undefined;
|
|
779
794
|
if (entry?.kind === "thinking" && entry.status !== "done") {
|
|
780
795
|
entry.status = "done";
|
|
796
|
+
entry.finishedAt ??= Date.now();
|
|
781
797
|
this.touchEntry(entry);
|
|
782
798
|
}
|
|
783
799
|
this.currentThinkingEntryId = undefined;
|
|
800
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
801
|
+
this.stopThinkingElapsedRenderTimer();
|
|
784
802
|
}
|
|
785
803
|
reconcileThinkingText(content) {
|
|
786
804
|
let entry = this.currentThinkingEntryId
|
|
@@ -788,12 +806,15 @@ export class AppSessionEventController {
|
|
|
788
806
|
: undefined;
|
|
789
807
|
if (!entry || entry.kind !== "thinking") {
|
|
790
808
|
const level = this.currentThinkingLevel();
|
|
809
|
+
const startedAt = this.currentThinkingEntryStartedAt ?? Date.now();
|
|
810
|
+
this.currentThinkingEntryStartedAt = startedAt;
|
|
791
811
|
entry = {
|
|
792
812
|
id: createId("thinking"),
|
|
793
813
|
kind: "thinking",
|
|
794
814
|
text: "",
|
|
795
815
|
expanded: this.host.toolDefaultExpanded(THINKING_TOOL_NAME),
|
|
796
816
|
...(level === undefined ? {} : { level }),
|
|
817
|
+
startedAt,
|
|
797
818
|
status: "running",
|
|
798
819
|
};
|
|
799
820
|
this.addEntry(entry);
|
|
@@ -806,13 +827,63 @@ export class AppSessionEventController {
|
|
|
806
827
|
delete entry.level;
|
|
807
828
|
else
|
|
808
829
|
entry.level = level;
|
|
830
|
+
entry.startedAt ??= this.currentThinkingEntryStartedAt ?? Date.now();
|
|
831
|
+
this.currentThinkingEntryStartedAt = entry.startedAt;
|
|
832
|
+
delete entry.finishedAt;
|
|
809
833
|
entry.status = "running";
|
|
834
|
+
this.startThinkingElapsedRenderTimer();
|
|
810
835
|
this.touchEntry(entry);
|
|
811
836
|
}
|
|
812
837
|
}
|
|
838
|
+
syncThinkingElapsedRenderTimer() {
|
|
839
|
+
const entry = this.currentThinkingEntryId ? this.findEntry(this.currentThinkingEntryId) : undefined;
|
|
840
|
+
if (entry?.kind === "thinking" && entry.status === "running" && entry.startedAt !== undefined) {
|
|
841
|
+
this.startThinkingElapsedRenderTimer();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.stopThinkingElapsedRenderTimer();
|
|
845
|
+
}
|
|
846
|
+
startThinkingElapsedRenderTimer() {
|
|
847
|
+
if (this.thinkingElapsedRenderTimer)
|
|
848
|
+
return;
|
|
849
|
+
this.thinkingElapsedRenderTimer = setInterval(() => {
|
|
850
|
+
if (!this.host.isRunning()) {
|
|
851
|
+
this.stopThinkingElapsedRenderTimer();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
this.host.scheduleRender();
|
|
855
|
+
}, 1000);
|
|
856
|
+
this.thinkingElapsedRenderTimer.unref?.();
|
|
857
|
+
}
|
|
858
|
+
stopThinkingElapsedRenderTimer() {
|
|
859
|
+
if (!this.thinkingElapsedRenderTimer)
|
|
860
|
+
return;
|
|
861
|
+
clearInterval(this.thinkingElapsedRenderTimer);
|
|
862
|
+
this.thinkingElapsedRenderTimer = undefined;
|
|
863
|
+
}
|
|
813
864
|
currentThinkingLevel() {
|
|
814
865
|
return this.host.runtime()?.session.thinkingLevel;
|
|
815
866
|
}
|
|
867
|
+
reconcileAssistantTextFromFinalMessage(message) {
|
|
868
|
+
const openContentIndex = this.currentAssistantTextBlockContentIndex;
|
|
869
|
+
if (openContentIndex !== undefined) {
|
|
870
|
+
const content = assistantTextContentAt(message, openContentIndex);
|
|
871
|
+
if (content !== undefined)
|
|
872
|
+
this.reconcileAssistantTextBlock(content, openContentIndex);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const textBlocks = assistantTextContents(message);
|
|
876
|
+
if (textBlocks.length !== 1)
|
|
877
|
+
return;
|
|
878
|
+
const entry = this.currentAssistantEntryId ? this.findEntry(this.currentAssistantEntryId) : undefined;
|
|
879
|
+
if (entry?.kind !== "assistant")
|
|
880
|
+
return;
|
|
881
|
+
const visibleText = assistantStreamVisibleTextForCompleteBlock(textBlocks[0] ?? "", false);
|
|
882
|
+
if (!visibleText || entry.text === visibleText)
|
|
883
|
+
return;
|
|
884
|
+
entry.text = visibleText;
|
|
885
|
+
this.touchEntry(entry);
|
|
886
|
+
}
|
|
816
887
|
renderAssistantToolCallsFromMessage(message) {
|
|
817
888
|
if (!isRecord(message) || !Array.isArray(message.content))
|
|
818
889
|
return;
|
|
@@ -893,6 +964,8 @@ export class AppSessionEventController {
|
|
|
893
964
|
this.currentAssistantTextBlockStartLength = undefined;
|
|
894
965
|
this.currentAssistantTextBlockContentIndex = undefined;
|
|
895
966
|
this.currentThinkingEntryId = undefined;
|
|
967
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
968
|
+
this.stopThinkingElapsedRenderTimer();
|
|
896
969
|
this.assistantTextBuffer = "";
|
|
897
970
|
this.assistantTextBlocksByContentIndex.clear();
|
|
898
971
|
this.finalizedToolCallContentIndexes.clear();
|
|
@@ -916,6 +989,11 @@ function assistantTextContentAt(value, contentIndex) {
|
|
|
916
989
|
const block = value.content[contentIndex];
|
|
917
990
|
return isRecord(block) && block.type === "text" && typeof block.text === "string" ? block.text : undefined;
|
|
918
991
|
}
|
|
992
|
+
function assistantTextContents(value) {
|
|
993
|
+
if (!isRecord(value) || !Array.isArray(value.content))
|
|
994
|
+
return [];
|
|
995
|
+
return value.content.flatMap((block) => (isRecord(block) && block.type === "text" && typeof block.text === "string" ? [block.text] : []));
|
|
996
|
+
}
|
|
919
997
|
function assistantStreamVisibleTextForCompleteBlock(text, hasVisibleTextBeforeBlock) {
|
|
920
998
|
let buffer = text;
|
|
921
999
|
let visibleText = "";
|
|
@@ -61,6 +61,7 @@ export declare class AppSessionLifecycleController {
|
|
|
61
61
|
constructor(host: AppSessionLifecycleHost);
|
|
62
62
|
start(): Promise<void>;
|
|
63
63
|
bindCurrentSession(options?: BindCurrentSessionOptions): Promise<void>;
|
|
64
|
+
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime | undefined): Promise<void>;
|
|
64
65
|
unsubscribeSession(): void;
|
|
65
66
|
afterSessionReplacement(message?: string): void;
|
|
66
67
|
private loadReplacementHistory;
|
|
@@ -85,6 +85,13 @@ export class AppSessionLifecycleController {
|
|
|
85
85
|
}
|
|
86
86
|
await bindPromise;
|
|
87
87
|
}
|
|
88
|
+
async awaitCurrentSessionExtensions(runtime = this.host.runtime()) {
|
|
89
|
+
if (!runtime)
|
|
90
|
+
return;
|
|
91
|
+
if (this.extensionBindRuntime !== runtime || this.extensionBindSession !== runtime.session)
|
|
92
|
+
return;
|
|
93
|
+
await this.extensionBindPromise;
|
|
94
|
+
}
|
|
88
95
|
unsubscribeSession() {
|
|
89
96
|
this.unsubscribe?.();
|
|
90
97
|
}
|
|
@@ -18,6 +18,7 @@ export type AppTabsControllerHost = {
|
|
|
18
18
|
runtime(): AgentSessionRuntime | undefined;
|
|
19
19
|
createRuntimeForNewSession(): Promise<AgentSessionRuntime>;
|
|
20
20
|
createRuntimeForSession(sessionPath: string): Promise<AgentSessionRuntime>;
|
|
21
|
+
awaitCurrentSessionExtensions?(runtime?: AgentSessionRuntime): Promise<void>;
|
|
21
22
|
activateRuntime(runtime: AgentSessionRuntime, options?: BindCurrentSessionOptions): Promise<void>;
|
|
22
23
|
disposeRuntime(runtime: AgentSessionRuntime): Promise<void>;
|
|
23
24
|
isRunning(): boolean;
|
|
@@ -641,7 +641,9 @@ export class AppTabsController {
|
|
|
641
641
|
void this.saveTabs();
|
|
642
642
|
this.scheduleTabPrewarm();
|
|
643
643
|
const cachedView = this.sessionViewsByTabId.get(target.id);
|
|
644
|
-
|
|
644
|
+
const cachedViewNeedsHistoryReload = this.tabIdsNeedingHistoryReload.has(target.id)
|
|
645
|
+
&& this.sessionActivity(targetRuntime.session) !== "running";
|
|
646
|
+
if (cachedView && this.host.restoreSessionView && !cachedViewNeedsHistoryReload) {
|
|
645
647
|
this.host.restoreSessionView(cachedView);
|
|
646
648
|
this.restoreDeferredUserMessages(target.id);
|
|
647
649
|
this.host.setSessionStatus(targetRuntime.session);
|
|
@@ -728,6 +730,7 @@ export class AppTabsController {
|
|
|
728
730
|
this.activeTabId = tab.id;
|
|
729
731
|
this.host.setStatus("starting new session");
|
|
730
732
|
this.host.render();
|
|
733
|
+
await this.host.awaitCurrentSessionExtensions?.(runtime);
|
|
731
734
|
const result = await runtime.newSession();
|
|
732
735
|
if (result.cancelled) {
|
|
733
736
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "New session cancelled." });
|
|
@@ -13,7 +13,9 @@ export declare class AppSubagentsWidgetController {
|
|
|
13
13
|
private pollTimer;
|
|
14
14
|
private pollInFlight;
|
|
15
15
|
private currentRunDir;
|
|
16
|
+
private currentRunDirs;
|
|
16
17
|
private state;
|
|
18
|
+
private readonly runFreshnessByRunDir;
|
|
17
19
|
private readonly taskPreviewsByRunDir;
|
|
18
20
|
private readonly snapshotByRunDir;
|
|
19
21
|
private refreshGeneration;
|
|
@@ -32,10 +34,16 @@ export declare class AppSubagentsWidgetController {
|
|
|
32
34
|
private schedulePoll;
|
|
33
35
|
private poll;
|
|
34
36
|
private refreshFromFiles;
|
|
35
|
-
private
|
|
37
|
+
private findActiveRegistryRunDirsForCurrentSession;
|
|
36
38
|
private registryRunDirsNewestFirst;
|
|
37
|
-
private clearMissingRunAndMaybeSelectReplacement;
|
|
38
39
|
private clearCachedRun;
|
|
40
|
+
private buildStateFromRuns;
|
|
41
|
+
private orderRuns;
|
|
42
|
+
private orderRunDirs;
|
|
43
|
+
private rememberRunFreshness;
|
|
44
|
+
private runFreshness;
|
|
45
|
+
private runFreshnessFromAgents;
|
|
46
|
+
private parseRunTimestamp;
|
|
39
47
|
private updateState;
|
|
40
48
|
private isCurrentGeneration;
|
|
41
49
|
private shouldContinuePolling;
|
|
@@ -10,7 +10,9 @@ export class AppSubagentsWidgetController {
|
|
|
10
10
|
pollTimer;
|
|
11
11
|
pollInFlight = false;
|
|
12
12
|
currentRunDir;
|
|
13
|
+
currentRunDirs = [];
|
|
13
14
|
state;
|
|
15
|
+
runFreshnessByRunDir = new Map();
|
|
14
16
|
taskPreviewsByRunDir = new Map();
|
|
15
17
|
snapshotByRunDir = new Map();
|
|
16
18
|
refreshGeneration = 0;
|
|
@@ -38,6 +40,8 @@ export class AppSubagentsWidgetController {
|
|
|
38
40
|
reset() {
|
|
39
41
|
this.refreshGeneration++;
|
|
40
42
|
this.currentRunDir = undefined;
|
|
43
|
+
this.currentRunDirs = [];
|
|
44
|
+
this.runFreshnessByRunDir.clear();
|
|
41
45
|
this.taskPreviewsByRunDir.clear();
|
|
42
46
|
this.snapshotByRunDir.clear();
|
|
43
47
|
this.updateState(undefined);
|
|
@@ -59,15 +63,14 @@ export class AppSubagentsWidgetController {
|
|
|
59
63
|
}
|
|
60
64
|
this.currentRunDir = runDir;
|
|
61
65
|
this.snapshotByRunDir.set(runDir, normalizedDetails);
|
|
66
|
+
this.rememberRunFreshness(runDir, this.runFreshnessFromAgents(normalizedDetails.agents) ?? Date.now());
|
|
62
67
|
if (activeSubagentStates(normalizedDetails.agents).length > 0) {
|
|
63
|
-
this.
|
|
64
|
-
|
|
65
|
-
agents: normalizedDetails.agents,
|
|
66
|
-
...(normalizedDetails.tasks === undefined ? {} : { tasks: normalizedDetails.tasks }),
|
|
68
|
+
this.currentRunDirs = this.orderRunDirs([runDir, ...this.currentRunDirs]);
|
|
69
|
+
this.updateState(this.buildStateFromRuns([normalizedDetails], {
|
|
67
70
|
live: false,
|
|
68
71
|
snapshotOnly: true,
|
|
69
72
|
checkedAt: Date.now(),
|
|
70
|
-
};
|
|
73
|
+
}));
|
|
71
74
|
}
|
|
72
75
|
void this.refreshFromFiles(this.refreshGeneration);
|
|
73
76
|
this.schedulePoll(0);
|
|
@@ -88,6 +91,7 @@ export class AppSubagentsWidgetController {
|
|
|
88
91
|
}))
|
|
89
92
|
.filter((run) => activeSubagentStates(run.agents).length > 0);
|
|
90
93
|
for (const run of activeRuns) {
|
|
94
|
+
this.rememberRunFreshness(run.runDir, this.runFreshnessFromAgents(run.agents) ?? data.checkedAt);
|
|
91
95
|
this.snapshotByRunDir.set(run.runDir, {
|
|
92
96
|
runDir: run.runDir,
|
|
93
97
|
agents: run.agents,
|
|
@@ -97,22 +101,21 @@ export class AppSubagentsWidgetController {
|
|
|
97
101
|
if (run.tasks)
|
|
98
102
|
this.taskPreviewsByRunDir.set(run.runDir, run.tasks);
|
|
99
103
|
}
|
|
100
|
-
|
|
101
|
-
if (!preferred) {
|
|
104
|
+
if (activeRuns.length === 0) {
|
|
102
105
|
this.currentRunDir = undefined;
|
|
106
|
+
this.currentRunDirs = [];
|
|
103
107
|
this.updateState(undefined);
|
|
104
108
|
this.stopPolling();
|
|
105
109
|
return;
|
|
106
110
|
}
|
|
107
|
-
|
|
108
|
-
this.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
...(preferred.tasks === undefined ? {} : { tasks: preferred.tasks }),
|
|
111
|
+
const orderedRuns = this.orderRuns(activeRuns);
|
|
112
|
+
this.currentRunDir = orderedRuns[0]?.runDir;
|
|
113
|
+
this.currentRunDirs = orderedRuns.map((run) => run.runDir);
|
|
114
|
+
this.updateState(this.buildStateFromRuns(orderedRuns, {
|
|
112
115
|
live: true,
|
|
113
116
|
snapshotOnly: false,
|
|
114
117
|
checkedAt: data.checkedAt,
|
|
115
|
-
});
|
|
118
|
+
}));
|
|
116
119
|
this.startFileWatcher();
|
|
117
120
|
this.schedulePoll(SUBAGENTS_POLL_INTERVAL_MS);
|
|
118
121
|
}
|
|
@@ -182,71 +185,77 @@ export class AppSubagentsWidgetController {
|
|
|
182
185
|
}
|
|
183
186
|
}
|
|
184
187
|
async refreshFromFiles(generation = this.refreshGeneration) {
|
|
185
|
-
|
|
186
|
-
if (!
|
|
187
|
-
const registry = await readSubagentRegistry(this.host.cwd);
|
|
188
|
-
if (!this.isCurrentGeneration(generation))
|
|
189
|
-
return;
|
|
190
|
-
runDir = await this.findActiveRegistryRunDirForCurrentSession(registry, generation);
|
|
191
|
-
if (!this.isCurrentGeneration(generation))
|
|
192
|
-
return;
|
|
193
|
-
if (runDir)
|
|
194
|
-
this.currentRunDir = runDir;
|
|
195
|
-
}
|
|
196
|
-
if (!runDir) {
|
|
197
|
-
if (!this.isCurrentGeneration(generation))
|
|
198
|
-
return;
|
|
199
|
-
this.updateState(undefined);
|
|
188
|
+
const registry = await readSubagentRegistry(this.host.cwd);
|
|
189
|
+
if (!this.isCurrentGeneration(generation))
|
|
200
190
|
return;
|
|
201
|
-
|
|
202
|
-
this.currentRunDir = runDir;
|
|
203
|
-
const fileState = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
191
|
+
const runDirs = await this.findActiveRegistryRunDirsForCurrentSession(registry, generation);
|
|
204
192
|
if (!this.isCurrentGeneration(generation))
|
|
205
193
|
return;
|
|
206
|
-
if (
|
|
207
|
-
|
|
194
|
+
if (runDirs.length === 0) {
|
|
195
|
+
this.currentRunDir = undefined;
|
|
196
|
+
this.currentRunDirs = [];
|
|
197
|
+
this.updateState(undefined);
|
|
208
198
|
return;
|
|
209
199
|
}
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
200
|
+
const runs = [];
|
|
201
|
+
for (const runDir of runDirs) {
|
|
202
|
+
const fileState = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
203
|
+
if (!this.isCurrentGeneration(generation))
|
|
204
|
+
return;
|
|
205
|
+
if (!fileState) {
|
|
213
206
|
this.clearCachedRun(runDir);
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const activeAgents = activeSubagentStates(fileState.agents);
|
|
210
|
+
if (activeAgents.length === 0) {
|
|
211
|
+
if (allSubagentStatesTerminal(fileState.agents) || fileState.agents.length === 0)
|
|
212
|
+
this.clearCachedRun(runDir);
|
|
213
|
+
continue;
|
|
216
214
|
}
|
|
215
|
+
const tasks = this.taskPreviewsByRunDir.get(runDir);
|
|
216
|
+
runs.push({
|
|
217
|
+
runDir,
|
|
218
|
+
agents: fileState.agents,
|
|
219
|
+
...(tasks === undefined ? {} : { tasks }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
if (runs.length === 0) {
|
|
223
|
+
this.currentRunDir = undefined;
|
|
224
|
+
this.currentRunDirs = [];
|
|
225
|
+
this.updateState(undefined);
|
|
217
226
|
return;
|
|
218
227
|
}
|
|
219
|
-
const
|
|
220
|
-
this.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
...(tasks === undefined ? {} : { tasks }),
|
|
228
|
+
const orderedRuns = this.orderRuns(runs);
|
|
229
|
+
this.currentRunDir = orderedRuns[0]?.runDir;
|
|
230
|
+
this.currentRunDirs = orderedRuns.map((run) => run.runDir);
|
|
231
|
+
this.updateState(this.buildStateFromRuns(orderedRuns, {
|
|
224
232
|
live: true,
|
|
225
233
|
snapshotOnly: false,
|
|
226
234
|
checkedAt: Date.now(),
|
|
227
|
-
});
|
|
235
|
+
}));
|
|
228
236
|
}
|
|
229
|
-
async
|
|
237
|
+
async findActiveRegistryRunDirsForCurrentSession(registry, generation) {
|
|
230
238
|
if (!registry)
|
|
231
|
-
return
|
|
239
|
+
return [];
|
|
232
240
|
const sessionFile = this.host.sessionFile();
|
|
233
241
|
if (!sessionFile)
|
|
234
|
-
return
|
|
242
|
+
return [];
|
|
235
243
|
const candidateRunDirs = this.registryRunDirsNewestFirst(registry);
|
|
244
|
+
const matchingRunDirs = [];
|
|
236
245
|
for (const runDir of candidateRunDirs) {
|
|
237
246
|
if (!this.isCurrentGeneration(generation))
|
|
238
|
-
return
|
|
247
|
+
return [];
|
|
239
248
|
if (!(await subagentRunHasParentSession(runDir, sessionFile)))
|
|
240
249
|
continue;
|
|
241
250
|
if (!this.isCurrentGeneration(generation))
|
|
242
|
-
return
|
|
251
|
+
return [];
|
|
243
252
|
const state = await readSubagentRunStateFromFiles(runDir, { includeLineCounts: false });
|
|
244
253
|
if (!this.isCurrentGeneration(generation))
|
|
245
|
-
return
|
|
254
|
+
return [];
|
|
246
255
|
if (activeSubagentStates(state?.agents ?? []).length > 0)
|
|
247
|
-
|
|
256
|
+
matchingRunDirs.push(runDir);
|
|
248
257
|
}
|
|
249
|
-
return
|
|
258
|
+
return matchingRunDirs;
|
|
250
259
|
}
|
|
251
260
|
registryRunDirsNewestFirst(registry) {
|
|
252
261
|
const runDirs = [];
|
|
@@ -263,30 +272,89 @@ export class AppSubagentsWidgetController {
|
|
|
263
272
|
addRunDir(registry.latestRunDir);
|
|
264
273
|
Object.values(registry.runs)
|
|
265
274
|
.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt))
|
|
266
|
-
.forEach((run) =>
|
|
275
|
+
.forEach((run) => {
|
|
276
|
+
this.rememberRunFreshness(run.runDir, this.parseRunTimestamp(run.updatedAt) ?? this.parseRunTimestamp(run.createdAt));
|
|
277
|
+
addRunDir(run.runDir);
|
|
278
|
+
});
|
|
267
279
|
return runDirs;
|
|
268
280
|
}
|
|
269
|
-
async clearMissingRunAndMaybeSelectReplacement(runDir, generation) {
|
|
270
|
-
this.clearCachedRun(runDir);
|
|
271
|
-
if (this.currentRunDir === runDir)
|
|
272
|
-
this.currentRunDir = undefined;
|
|
273
|
-
const registry = await readSubagentRegistry(this.host.cwd);
|
|
274
|
-
if (!this.isCurrentGeneration(generation))
|
|
275
|
-
return;
|
|
276
|
-
const replacementRunDir = await this.findActiveRegistryRunDirForCurrentSession(registry, generation);
|
|
277
|
-
if (!this.isCurrentGeneration(generation))
|
|
278
|
-
return;
|
|
279
|
-
if (replacementRunDir) {
|
|
280
|
-
this.currentRunDir = replacementRunDir;
|
|
281
|
-
await this.refreshFromFiles(generation);
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
this.updateState(undefined);
|
|
285
|
-
}
|
|
286
281
|
clearCachedRun(runDir) {
|
|
282
|
+
this.runFreshnessByRunDir.delete(runDir);
|
|
287
283
|
this.snapshotByRunDir.delete(runDir);
|
|
288
284
|
this.taskPreviewsByRunDir.delete(runDir);
|
|
289
285
|
}
|
|
286
|
+
buildStateFromRuns(runs, meta) {
|
|
287
|
+
const activeRuns = runs
|
|
288
|
+
.map((run) => ({ ...run, agents: activeSubagentStates(run.agents) }))
|
|
289
|
+
.filter((run) => run.agents.length > 0);
|
|
290
|
+
if (activeRuns.length === 0)
|
|
291
|
+
return undefined;
|
|
292
|
+
const primary = activeRuns[0];
|
|
293
|
+
if (!primary)
|
|
294
|
+
return undefined;
|
|
295
|
+
return {
|
|
296
|
+
runs: activeRuns.map((run) => ({
|
|
297
|
+
runDir: run.runDir,
|
|
298
|
+
agents: run.agents,
|
|
299
|
+
...(run.tasks === undefined ? {} : { tasks: run.tasks }),
|
|
300
|
+
})),
|
|
301
|
+
runDir: primary.runDir,
|
|
302
|
+
agents: activeRuns.flatMap((run) => run.agents),
|
|
303
|
+
...(primary.tasks === undefined ? {} : { tasks: primary.tasks }),
|
|
304
|
+
...meta,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
orderRuns(runs) {
|
|
308
|
+
const order = this.orderRunDirs(runs.map((run) => run.runDir));
|
|
309
|
+
const priority = new Map(order.map((runDir, index) => [runDir, index]));
|
|
310
|
+
return [...runs].sort((a, b) => {
|
|
311
|
+
const freshness = this.runFreshness(b) - this.runFreshness(a);
|
|
312
|
+
if (freshness !== 0)
|
|
313
|
+
return freshness;
|
|
314
|
+
return (priority.get(a.runDir) ?? Number.MAX_SAFE_INTEGER) - (priority.get(b.runDir) ?? Number.MAX_SAFE_INTEGER);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
orderRunDirs(runDirs) {
|
|
318
|
+
const seen = new Set();
|
|
319
|
+
const preferred = [this.currentRunDir, ...this.currentRunDirs, ...runDirs].filter((runDir) => Boolean(runDir));
|
|
320
|
+
const ordered = [];
|
|
321
|
+
for (const runDir of preferred) {
|
|
322
|
+
if (seen.has(runDir))
|
|
323
|
+
continue;
|
|
324
|
+
seen.add(runDir);
|
|
325
|
+
ordered.push(runDir);
|
|
326
|
+
}
|
|
327
|
+
return ordered;
|
|
328
|
+
}
|
|
329
|
+
rememberRunFreshness(runDir, freshness) {
|
|
330
|
+
if (!Number.isFinite(freshness))
|
|
331
|
+
return;
|
|
332
|
+
const next = freshness ?? 0;
|
|
333
|
+
const previous = this.runFreshnessByRunDir.get(runDir) ?? Number.NEGATIVE_INFINITY;
|
|
334
|
+
if (next >= previous)
|
|
335
|
+
this.runFreshnessByRunDir.set(runDir, next);
|
|
336
|
+
}
|
|
337
|
+
runFreshness(run) {
|
|
338
|
+
return this.runFreshnessFromAgents(run.agents)
|
|
339
|
+
?? this.runFreshnessByRunDir.get(run.runDir)
|
|
340
|
+
?? 0;
|
|
341
|
+
}
|
|
342
|
+
runFreshnessFromAgents(agents) {
|
|
343
|
+
let freshest;
|
|
344
|
+
for (const agent of agents) {
|
|
345
|
+
const parsed = this.parseRunTimestamp(agent.startedAt);
|
|
346
|
+
if (parsed === undefined)
|
|
347
|
+
continue;
|
|
348
|
+
freshest = freshest === undefined ? parsed : Math.max(freshest, parsed);
|
|
349
|
+
}
|
|
350
|
+
return freshest;
|
|
351
|
+
}
|
|
352
|
+
parseRunTimestamp(value) {
|
|
353
|
+
if (!value)
|
|
354
|
+
return undefined;
|
|
355
|
+
const parsed = Date.parse(value);
|
|
356
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
357
|
+
}
|
|
290
358
|
updateState(next) {
|
|
291
359
|
const previous = stringifyUnknown(this.state);
|
|
292
360
|
const serializedNext = stringifyUnknown(next);
|
|
@@ -298,6 +366,9 @@ export class AppSubagentsWidgetController {
|
|
|
298
366
|
return generation === this.refreshGeneration;
|
|
299
367
|
}
|
|
300
368
|
shouldContinuePolling() {
|
|
369
|
+
const runs = this.state?.runs;
|
|
370
|
+
if (runs?.length)
|
|
371
|
+
return runs.some((run) => activeSubagentStates(run.agents).length > 0);
|
|
301
372
|
return activeSubagentStates(this.state?.agents ?? []).length > 0;
|
|
302
373
|
}
|
|
303
374
|
eventMatchesCurrentSession(eventSessionFile) {
|
|
@@ -24,6 +24,10 @@ export declare class AppTerminalController {
|
|
|
24
24
|
private terminalEnabled;
|
|
25
25
|
private interactiveSuspended;
|
|
26
26
|
private stopPromise;
|
|
27
|
+
private keyboardProtocolNegotiationBuffer;
|
|
28
|
+
private keyboardProtocolBufferFlushTimer;
|
|
29
|
+
private kittyProtocolActive;
|
|
30
|
+
private modifyOtherKeysActive;
|
|
27
31
|
private readonly enterInteractiveSequence;
|
|
28
32
|
private readonly exitInteractiveSequence;
|
|
29
33
|
constructor(host: AppTerminalControllerHost);
|
|
@@ -40,4 +44,10 @@ export declare class AppTerminalController {
|
|
|
40
44
|
private scheduleForcedProcessExit;
|
|
41
45
|
private readonly onResize;
|
|
42
46
|
private readonly onInputData;
|
|
47
|
+
private beginKeyboardProtocolNegotiation;
|
|
48
|
+
private filterKeyboardProtocolNegotiationInput;
|
|
49
|
+
private handleKeyboardProtocolNegotiationResponse;
|
|
50
|
+
private enableModifyOtherKeysFallback;
|
|
51
|
+
private setKeyboardProtocolNegotiationBuffer;
|
|
52
|
+
private clearKeyboardProtocolNegotiationBuffer;
|
|
43
53
|
}
|