@selesai/code 0.1.18 → 0.2.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.
- package/dist/agents/architect.md +4 -3
- package/dist/agents/builder.md +5 -4
- package/dist/agents/commentator.md +3 -2
- package/dist/agents/explorer.md +4 -4
- package/dist/agents/recapper.md +4 -3
- package/dist/config.d.ts +6 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -9
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +3 -1
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +2 -0
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +8 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +25 -11
- package/dist/core/skills.js.map +1 -1
- package/dist/defaults/settings.json +2 -29
- package/dist/extensions/package.json +5 -4
- package/dist/extensions/pi-intercom/index.ts +12 -5
- package/dist/extensions/pi-powerline-footer/index.ts +4 -1
- package/dist/extensions/pi-powerline-footer/tps.ts +286 -0
- package/dist/extensions/pi-powerline-footer/welcome.ts +47 -46
- package/dist/extensions/pi-subagents/README.md +12 -5
- package/dist/extensions/pi-subagents/agents/context-builder.md +2 -2
- package/dist/extensions/pi-subagents/agents/researcher.md +4 -5
- package/dist/extensions/pi-subagents/package.json +8 -5
- package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
- package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
- package/dist/extensions/pi-subagents/src/agents/agent-management.ts +1 -1
- package/dist/extensions/pi-subagents/src/agents/agents.ts +3 -3
- package/dist/extensions/pi-subagents/src/extension/control-notices.ts +1 -1
- package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
- package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +1 -1
- package/dist/extensions/pi-subagents/src/extension/index.ts +29 -2
- package/dist/extensions/pi-subagents/src/profiles/profiles.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +72 -13
- package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/notify.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +85 -41
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +1 -1
- package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +46 -3
- package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +1 -3
- package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +18 -18
- package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +24 -24
- package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +46 -0
- package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +2 -2
- package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -4
- package/dist/extensions/pi-subagents/src/shared/fork-context.ts +1 -1
- package/dist/extensions/pi-subagents/src/shared/types.ts +6 -6
- package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -3
- package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -1
- package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +1 -1
- package/dist/extensions/pi-subagents/src/tui/render.ts +1 -1
- package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +45 -0
- package/dist/extensions/pi-subagents/tsconfig.runner.json +17 -0
- package/dist/extensions/pi-web-agent/package.json +31 -0
- package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -0
- package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
- package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -0
- package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -0
- package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -0
- package/dist/extensions/pi-web-agent/src/changelog-notice.ts +133 -0
- package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -0
- package/dist/extensions/pi-web-agent/src/extension.ts +126 -0
- package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -0
- package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -0
- package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -0
- package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -0
- package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -0
- package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -0
- package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -0
- package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -0
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -0
- package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -0
- package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -0
- package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -0
- package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -0
- package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -0
- package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -0
- package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +208 -0
- package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -0
- package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -0
- package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -0
- package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -0
- package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -0
- package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -0
- package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -0
- package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -0
- package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -0
- package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -0
- package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -0
- package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -0
- package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -0
- package/dist/extensions/pi-web-agent/src/types.ts +88 -0
- package/dist/extensions/ponytail/index.js +189 -0
- package/dist/extensions/ponytail/package.json +8 -0
- package/dist/extensions/ponytail/ponytail-config.cjs +122 -0
- package/dist/extensions/ponytail/ponytail-instructions.cjs +94 -0
- package/dist/extensions/ponytail/test/extension.test.js +167 -0
- package/dist/extensions/ponytail/test/helpers.test.js +92 -0
- package/dist/extensions/question/constants.ts +38 -0
- package/dist/extensions/question/dialog-adapter.ts +74 -0
- package/dist/extensions/question/helpers.ts +102 -0
- package/dist/extensions/question/index.ts +671 -0
- package/dist/extensions/question/navigation.ts +14 -0
- package/dist/extensions/question/package.json +18 -0
- package/dist/extensions/question/question-list.ts +498 -0
- package/dist/extensions/question/row-layout.ts +162 -0
- package/dist/extensions/question/schemas.ts +69 -0
- package/dist/extensions/question/selection-mode.ts +49 -0
- package/dist/extensions/question/shortcuts.ts +53 -0
- package/dist/extensions/question/tests/helpers.test.ts +178 -0
- package/dist/extensions/question/tests/question-list.test.ts +230 -0
- package/dist/extensions/question/tests/row-layout.test.ts +136 -0
- package/dist/extensions/question/tests/shortcuts.test.ts +85 -0
- package/dist/extensions/question/tests/ui-protocol.test.ts +143 -0
- package/dist/extensions/question/tui-adapter.ts +33 -0
- package/dist/extensions/question/types.ts +102 -0
- package/dist/extensions/question/ui-protocol.ts +46 -0
- package/dist/extensions/tokenin-onboarding.ts +265 -2
- package/dist/extensions/web-agent-onboarding.ts +222 -0
- package/dist/extensions/workflow/adapter.ts +588 -0
- package/dist/extensions/workflow/extension.ts +24 -0
- package/dist/extensions/workflow/modes/prototype.ts +156 -0
- package/dist/extensions/workflow/modes/quick.ts +138 -0
- package/dist/extensions/workflow/package.json +17 -0
- package/dist/extensions/workflow/state-machine.ts +386 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/mime.d.ts.map +1 -1
- package/dist/utils/mime.js +24 -1
- package/dist/utils/mime.js.map +1 -1
- package/docs/docs.json +4 -0
- package/docs/workflows.md +206 -0
- package/package.json +5 -1
- package/README.md +0 -198
|
@@ -20,27 +20,8 @@
|
|
|
20
20
|
"packages": [
|
|
21
21
|
"git:github.com/MasuRii/pi-tool-display",
|
|
22
22
|
"git:github.com/omaclaren/pi-markdown-preview",
|
|
23
|
-
"git:github.com/aliou/pi-guardrails"
|
|
24
|
-
"git:github.com/nicobailon/pi-intercom",
|
|
25
|
-
"git:github.com/nicobailon/pi-rewind-hook"
|
|
23
|
+
"git:github.com/aliou/pi-guardrails"
|
|
26
24
|
],
|
|
27
|
-
"powerline": {
|
|
28
|
-
"preset": "custom",
|
|
29
|
-
"fixedEditor": true,
|
|
30
|
-
"fixedEditorPromptGlyph": "",
|
|
31
|
-
"mouseScroll": true,
|
|
32
|
-
"customItems": [
|
|
33
|
-
{
|
|
34
|
-
"id": "tps",
|
|
35
|
-
"statusKey": "tps",
|
|
36
|
-
"position": "left",
|
|
37
|
-
"prefix": "",
|
|
38
|
-
"color": "success",
|
|
39
|
-
"hideWhenMissing": true,
|
|
40
|
-
"excludeFromExtensionStatuses": true
|
|
41
|
-
}
|
|
42
|
-
]
|
|
43
|
-
},
|
|
44
25
|
"quietStartup": true,
|
|
45
26
|
"retry": {
|
|
46
27
|
"enabled": true,
|
|
@@ -53,13 +34,5 @@
|
|
|
53
34
|
},
|
|
54
35
|
"theme": "dark",
|
|
55
36
|
"transport": "websocket",
|
|
56
|
-
"treeFilterMode": "no-tools"
|
|
57
|
-
"webSearch": {
|
|
58
|
-
"apiKeys": {
|
|
59
|
-
"BRAVE_API_KEY": "",
|
|
60
|
-
"EXA_API_KEY": "",
|
|
61
|
-
"JINA_API_KEY": "",
|
|
62
|
-
"TAVILY_API_KEY": ""
|
|
63
|
-
}
|
|
64
|
-
}
|
|
37
|
+
"treeFilterMode": "no-tools"
|
|
65
38
|
}
|
|
@@ -7,14 +7,15 @@
|
|
|
7
7
|
"extensions": [
|
|
8
8
|
"./copy-turn.ts",
|
|
9
9
|
"./gitignore-guard.ts",
|
|
10
|
-
"./
|
|
10
|
+
"./tool-error-autofix.ts",
|
|
11
|
+
"./question",
|
|
11
12
|
"./rtk.ts",
|
|
12
|
-
"./tps-tracker.ts",
|
|
13
13
|
"./tokenin-onboarding.ts",
|
|
14
14
|
"./undo.ts",
|
|
15
|
-
"./
|
|
16
|
-
"./quick.ts",
|
|
15
|
+
"./workflow",
|
|
17
16
|
"./pi-subagents",
|
|
17
|
+
"./pi-web-agent",
|
|
18
|
+
"./web-agent-onboarding.ts",
|
|
18
19
|
"./pi-powerline-footer",
|
|
19
20
|
"./pi-extension"
|
|
20
21
|
]
|
|
@@ -17,11 +17,11 @@ const SUBAGENT_RESULT_INTERCOM_DELIVERY_EVENT = "subagent:result-intercom-delive
|
|
|
17
17
|
const INBOUND_FLUSH_DELAY_MS = 200;
|
|
18
18
|
const INBOUND_IDLE_RETRY_MS = 500;
|
|
19
19
|
const DEFAULT_UNNAMED_SESSION_ALIAS_PREFIX = "subagent-chat";
|
|
20
|
-
const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "
|
|
21
|
-
const SUBAGENT_RUN_ID_ENV = "
|
|
22
|
-
const SUBAGENT_CHILD_AGENT_ENV = "
|
|
23
|
-
const SUBAGENT_CHILD_INDEX_ENV = "
|
|
24
|
-
const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "
|
|
20
|
+
const SUBAGENT_ORCHESTRATOR_TARGET_ENV = "SELESAI_SUBAGENT_ORCHESTRATOR_TARGET";
|
|
21
|
+
const SUBAGENT_RUN_ID_ENV = "SELESAI_SUBAGENT_RUN_ID";
|
|
22
|
+
const SUBAGENT_CHILD_AGENT_ENV = "SELESAI_SUBAGENT_CHILD_AGENT";
|
|
23
|
+
const SUBAGENT_CHILD_INDEX_ENV = "SELESAI_SUBAGENT_CHILD_INDEX";
|
|
24
|
+
const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "SELESAI_SUBAGENT_INTERCOM_SESSION_NAME";
|
|
25
25
|
|
|
26
26
|
interface ChildOrchestratorMetadata {
|
|
27
27
|
orchestratorTarget: string;
|
|
@@ -1006,6 +1006,13 @@ export default function piIntercomExtension(pi: ExtensionAPI) {
|
|
|
1006
1006
|
syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
1007
1007
|
replyTracker.beginTurn();
|
|
1008
1008
|
});
|
|
1009
|
+
pi.on("session_info_changed", (_event, ctx) => {
|
|
1010
|
+
if (!getLiveContext(ctx)) {
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
currentSessionId = ctx.sessionManager.getSessionId();
|
|
1014
|
+
syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
1015
|
+
});
|
|
1009
1016
|
pi.on("model_select", (event, ctx) => {
|
|
1010
1017
|
if (!getLiveContext(ctx)) {
|
|
1011
1018
|
return;
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
shortcutConflictKey,
|
|
44
44
|
shortcutUsesSuper,
|
|
45
45
|
} from "./shortcuts.ts";
|
|
46
|
-
import {
|
|
46
|
+
import {
|
|
47
47
|
initVibeManager,
|
|
48
48
|
onVibeBeforeAgentStart,
|
|
49
49
|
onVibeAgentStart,
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
getVibeFileCount,
|
|
60
60
|
generateVibesBatch,
|
|
61
61
|
} from "./working-vibes.ts";
|
|
62
|
+
import { setupTpsTracker } from "./tps.ts";
|
|
62
63
|
|
|
63
64
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
65
|
// Configuration
|
|
@@ -1175,6 +1176,8 @@ export default function powerlineFooter(pi: ExtensionAPI) {
|
|
|
1175
1176
|
}
|
|
1176
1177
|
|
|
1177
1178
|
// Track session start
|
|
1179
|
+
setupTpsTracker(pi);
|
|
1180
|
+
|
|
1178
1181
|
pi.on("session_start", async (event, ctx) => {
|
|
1179
1182
|
shellSession?.dispose();
|
|
1180
1183
|
shellSession = null;
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TPS Tracker — tokens-per-second tracking merged into the powerline footer.
|
|
3
|
+
*
|
|
4
|
+
* Writes live and final TPS stats to `ctx.ui.setStatus("tps", ...)`, which the
|
|
5
|
+
* powerline `extension_statuses` segment surfaces in the status bar (and the
|
|
6
|
+
* `setExtensionStatus` repaint hook turns into a re-render automatically).
|
|
7
|
+
*
|
|
8
|
+
* Tracks:
|
|
9
|
+
* - Main model output tokens vs. streaming wall time (excludes first-token
|
|
10
|
+
* latency and tool execution gaps).
|
|
11
|
+
* - pi-subagents child output tokens via tool_execution_* events, aggregated
|
|
12
|
+
* into a combined "main + subagents" TPS at agent_end.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
16
|
+
|
|
17
|
+
export function setupTpsTracker(pi: ExtensionAPI): void {
|
|
18
|
+
/** Timestamp when the current assistant message event started. Used as a fallback. */
|
|
19
|
+
let messageStart: number | null = null;
|
|
20
|
+
/** Timestamp of the first streamed output delta for the current assistant message. */
|
|
21
|
+
let streamStart: number | null = null;
|
|
22
|
+
/** Estimated streamed output tokens for live display before providers report final usage. */
|
|
23
|
+
let estimatedStreamedTokens = 0;
|
|
24
|
+
/** Cumulative official output tokens across all assistant messages in this agent run. */
|
|
25
|
+
let totalOutputTokens = 0;
|
|
26
|
+
/** Cumulative time (ms) spent actually streaming output deltas (excludes tool execution and first-token latency). */
|
|
27
|
+
let totalStreamMs = 0;
|
|
28
|
+
/** Subagent tool-call start times, used to include pi-subagents child output in aggregate TPS. */
|
|
29
|
+
const subagentStarts = new Map<string, number>();
|
|
30
|
+
/** Latest cumulative foreground subagent tokens seen during tool_execution_update. */
|
|
31
|
+
const subagentLiveTokens = new Map<string, number>();
|
|
32
|
+
/** Cumulative subagent output tokens in this agent run. */
|
|
33
|
+
let totalSubagentTokens = 0;
|
|
34
|
+
/** Cumulative subagent wall time (ms) in this agent run. */
|
|
35
|
+
let totalSubagentMs = 0;
|
|
36
|
+
/** Completed subagent call summaries in this agent run. */
|
|
37
|
+
const subagentSummaries: Array<{ tokens: number; ms: number; tps: number }> = [];
|
|
38
|
+
|
|
39
|
+
function asRecord(value: unknown): Record<string, unknown> | null {
|
|
40
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function numberValue(value: unknown): number {
|
|
44
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getDetailsResult(value: unknown): Record<string, unknown> {
|
|
48
|
+
const result = asRecord(value);
|
|
49
|
+
const details = asRecord(result?.details);
|
|
50
|
+
return details ?? result ?? {};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function getResults(value: unknown): Record<string, unknown>[] {
|
|
54
|
+
const details = getDetailsResult(value);
|
|
55
|
+
return Array.isArray(details.results)
|
|
56
|
+
? details.results.map(asRecord).filter((item): item is Record<string, unknown> => !!item)
|
|
57
|
+
: [];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function generatedTokensFromUsage(usage: Record<string, unknown> | null): number {
|
|
61
|
+
return numberValue(usage?.output ?? usage?.outputTokens ?? usage?.completionTokens ?? usage?.completion_tokens);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function generatedTokensFromMessage(message: { usage?: unknown; content?: unknown }): number {
|
|
65
|
+
const usageTokens = generatedTokensFromUsage(asRecord(message.usage));
|
|
66
|
+
if (usageTokens > 0) return usageTokens;
|
|
67
|
+
|
|
68
|
+
const content = Array.isArray(message.content) ? message.content : [];
|
|
69
|
+
const chars = content.reduce((sum, block) => {
|
|
70
|
+
const item = asRecord(block);
|
|
71
|
+
if (!item) return sum;
|
|
72
|
+
if (item.type === "text") return sum + String(item.text ?? "").length;
|
|
73
|
+
if (item.type === "thinking") return sum + String(item.thinking ?? "").length;
|
|
74
|
+
if (item.type === "toolCall") return sum + String(item.name ?? "").length + JSON.stringify(item.arguments ?? {}).length;
|
|
75
|
+
return sum;
|
|
76
|
+
}, 0);
|
|
77
|
+
return Math.ceil(chars / 4);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function outputTokensFromResult(value: unknown): number {
|
|
81
|
+
const results = getResults(value);
|
|
82
|
+
return results.reduce((sum, result) => sum + generatedTokensFromUsage(asRecord(result.usage)), 0);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function liveTokensFromResult(value: unknown): number {
|
|
86
|
+
return subagentProgressItems(value).reduce((sum, item) => sum + item.tokens, 0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function subagentProgressItems(value: unknown): Array<{ name: string; tokens: number; status?: string }> {
|
|
90
|
+
const details = getDetailsResult(value);
|
|
91
|
+
const progress = Array.isArray(details.progress) ? details.progress : undefined;
|
|
92
|
+
const resultItems = getResults(value).map((result, index) => {
|
|
93
|
+
const progress = asRecord(result.progress);
|
|
94
|
+
const usage = asRecord(result.usage);
|
|
95
|
+
return {
|
|
96
|
+
name: String(result.agent || progress?.agent || `agent${index + 1}`),
|
|
97
|
+
tokens: generatedTokensFromUsage(usage) || generatedTokensFromUsage(progress),
|
|
98
|
+
status: typeof progress?.status === "string" ? progress.status : undefined,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
if (!progress || resultItems.some((item) => item.tokens > 0)) return resultItems;
|
|
102
|
+
|
|
103
|
+
return progress.map((entry, index) => {
|
|
104
|
+
const item = asRecord(entry) ?? {};
|
|
105
|
+
return {
|
|
106
|
+
name: String(item.agent || `agent${index + 1}`),
|
|
107
|
+
tokens: generatedTokensFromUsage(item),
|
|
108
|
+
status: typeof item.status === "string" ? item.status : undefined,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function subagentModeLabel(value: unknown): string {
|
|
114
|
+
const details = getDetailsResult(value);
|
|
115
|
+
const mode = typeof details.mode === "string" ? details.mode : "subagents";
|
|
116
|
+
return mode === "parallel" ? "parallel" : mode === "chain" ? "chain" : "subagent";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
120
|
+
totalOutputTokens = 0;
|
|
121
|
+
totalStreamMs = 0;
|
|
122
|
+
totalSubagentTokens = 0;
|
|
123
|
+
totalSubagentMs = 0;
|
|
124
|
+
subagentSummaries.length = 0;
|
|
125
|
+
messageStart = null;
|
|
126
|
+
streamStart = null;
|
|
127
|
+
estimatedStreamedTokens = 0;
|
|
128
|
+
subagentStarts.clear();
|
|
129
|
+
subagentLiveTokens.clear();
|
|
130
|
+
const theme = ctx.ui.theme;
|
|
131
|
+
ctx.ui.setStatus("tps", theme.fg("success", "generating..."));
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
pi.on("message_start", async (event) => {
|
|
135
|
+
if (event.message.role !== "assistant") return;
|
|
136
|
+
messageStart = Date.now();
|
|
137
|
+
streamStart = null;
|
|
138
|
+
estimatedStreamedTokens = 0;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
pi.on("message_update", async (event, ctx) => {
|
|
142
|
+
if (event.message.role !== "assistant") return;
|
|
143
|
+
|
|
144
|
+
const streamEvent = event.assistantMessageEvent;
|
|
145
|
+
const isOutputDelta =
|
|
146
|
+
streamEvent.type === "text_delta" ||
|
|
147
|
+
streamEvent.type === "thinking_delta" ||
|
|
148
|
+
streamEvent.type === "toolcall_delta";
|
|
149
|
+
|
|
150
|
+
if (!isOutputDelta) return;
|
|
151
|
+
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
streamStart ??= now;
|
|
154
|
+
estimatedStreamedTokens += Math.max(0, streamEvent.delta.length / 4);
|
|
155
|
+
|
|
156
|
+
const elapsed = (now - streamStart) / 1000;
|
|
157
|
+
const officialTokens = generatedTokensFromUsage(asRecord(event.message.usage));
|
|
158
|
+
const currentTokens = officialTokens > 0 ? officialTokens : estimatedStreamedTokens;
|
|
159
|
+
|
|
160
|
+
if (elapsed > 0 && currentTokens > 0) {
|
|
161
|
+
const tps = Math.round(currentTokens / elapsed);
|
|
162
|
+
const tokenLabel = officialTokens > 0
|
|
163
|
+
? `${officialTokens} tok`
|
|
164
|
+
: `~${Math.round(estimatedStreamedTokens)} tok`;
|
|
165
|
+
const theme = ctx.ui.theme;
|
|
166
|
+
ctx.ui.setStatus(
|
|
167
|
+
"tps",
|
|
168
|
+
`${theme.fg("accent", `${tps} tok/s`)}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
pi.on("message_end", async (event) => {
|
|
174
|
+
if (event.message.role !== "assistant") return;
|
|
175
|
+
|
|
176
|
+
const messageTokens = generatedTokensFromMessage(event.message);
|
|
177
|
+
const timingStart = streamStart ?? messageStart;
|
|
178
|
+
if (!timingStart || messageTokens <= 0) {
|
|
179
|
+
messageStart = null;
|
|
180
|
+
streamStart = null;
|
|
181
|
+
estimatedStreamedTokens = 0;
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
totalOutputTokens += messageTokens;
|
|
186
|
+
totalStreamMs += Math.max(0, Date.now() - timingStart);
|
|
187
|
+
|
|
188
|
+
messageStart = null;
|
|
189
|
+
streamStart = null;
|
|
190
|
+
estimatedStreamedTokens = 0;
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
pi.on("tool_execution_start", async (event) => {
|
|
194
|
+
if (event.toolName !== "subagent") return;
|
|
195
|
+
subagentStarts.set(event.toolCallId, Date.now());
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
pi.on("tool_execution_update", async (event, ctx) => {
|
|
199
|
+
if (event.toolName !== "subagent") return;
|
|
200
|
+
|
|
201
|
+
const start = subagentStarts.get(event.toolCallId);
|
|
202
|
+
if (!start) return;
|
|
203
|
+
|
|
204
|
+
const items = subagentProgressItems(event.partialResult);
|
|
205
|
+
const tokens = items.reduce((sum, item) => sum + item.tokens, 0);
|
|
206
|
+
subagentLiveTokens.set(event.toolCallId, tokens);
|
|
207
|
+
const elapsed = (Date.now() - start) / 1000;
|
|
208
|
+
if (tokens <= 0 || elapsed <= 0) return;
|
|
209
|
+
|
|
210
|
+
const tps = Math.round(tokens / elapsed);
|
|
211
|
+
const mode = subagentModeLabel(event.partialResult);
|
|
212
|
+
const agentSummary = items
|
|
213
|
+
.filter((item) => item.tokens > 0 || item.status === "running")
|
|
214
|
+
.map((item) => `${item.name} ${Math.round(item.tokens / elapsed)} t/s`)
|
|
215
|
+
.join(" ");
|
|
216
|
+
const theme = ctx.ui.theme;
|
|
217
|
+
ctx.ui.setStatus("tps", `${theme.fg("accent", `main+${mode} ${tps} t/s`)} ${theme.fg("success", `(${agentSummary || `${tps} t/s`})`)}`);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
pi.on("tool_execution_end", async (event, ctx) => {
|
|
221
|
+
if (event.toolName !== "subagent") return;
|
|
222
|
+
|
|
223
|
+
const start = subagentStarts.get(event.toolCallId);
|
|
224
|
+
subagentStarts.delete(event.toolCallId);
|
|
225
|
+
const liveTokens = subagentLiveTokens.get(event.toolCallId) ?? liveTokensFromResult(event.result);
|
|
226
|
+
subagentLiveTokens.delete(event.toolCallId);
|
|
227
|
+
if (!start || event.isError) return;
|
|
228
|
+
|
|
229
|
+
const outputTokens = outputTokensFromResult(event.result);
|
|
230
|
+
const displayTokens = outputTokens || liveTokens;
|
|
231
|
+
const mode = subagentModeLabel(event.result);
|
|
232
|
+
const elapsedMs = Math.max(0, Date.now() - start);
|
|
233
|
+
if (displayTokens <= 0 || elapsedMs <= 0) return;
|
|
234
|
+
const elapsedSeconds = elapsedMs / 1000;
|
|
235
|
+
const items = subagentProgressItems(event.result);
|
|
236
|
+
const agentSummary = items
|
|
237
|
+
.filter((item) => item.tokens > 0)
|
|
238
|
+
.map((item) => `${item.name} ${Math.round(item.tokens / elapsedSeconds)} t/s`)
|
|
239
|
+
.join(" ");
|
|
240
|
+
|
|
241
|
+
totalOutputTokens += displayTokens;
|
|
242
|
+
totalStreamMs += elapsedMs;
|
|
243
|
+
totalSubagentTokens += displayTokens;
|
|
244
|
+
totalSubagentMs += elapsedMs;
|
|
245
|
+
|
|
246
|
+
const tps = Math.round(displayTokens / elapsedSeconds);
|
|
247
|
+
subagentSummaries.push({ tokens: displayTokens, ms: elapsedMs, tps });
|
|
248
|
+
const theme = ctx.ui.theme;
|
|
249
|
+
ctx.ui.notify(`${theme.fg("success", "✓")} ${theme.fg("accent", `${tps} t/s`)}`, "info");
|
|
250
|
+
ctx.ui.setStatus(
|
|
251
|
+
"tps",
|
|
252
|
+
`${theme.fg("accent", `main+${mode} ${tps} t/s`)} ${theme.fg("success", `(${agentSummary || `${tps} t/s`})`)}`,
|
|
253
|
+
);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
pi.on("agent_end", async (_event, ctx) => {
|
|
257
|
+
const elapsed = totalStreamMs / 1000;
|
|
258
|
+
const tps = totalOutputTokens > 0 && elapsed > 0 ? Math.round(totalOutputTokens / elapsed) : 0;
|
|
259
|
+
|
|
260
|
+
const theme = ctx.ui.theme;
|
|
261
|
+
const icon = theme.fg("success", "✓");
|
|
262
|
+
const tpsLabel = tps > 0
|
|
263
|
+
? theme.fg("accent", `${tps} t/s`)
|
|
264
|
+
: theme.fg("success", "N/A");
|
|
265
|
+
const mainTokens = totalOutputTokens - totalSubagentTokens;
|
|
266
|
+
const mainMs = totalStreamMs - totalSubagentMs;
|
|
267
|
+
const mainTps = mainTokens > 0 && mainMs > 0 ? Math.round(mainTokens / (mainMs / 1000)) : 0;
|
|
268
|
+
const subagentTps = totalSubagentTokens > 0 && totalSubagentMs > 0
|
|
269
|
+
? Math.round(totalSubagentTokens / (totalSubagentMs / 1000))
|
|
270
|
+
: 0;
|
|
271
|
+
const mainDetail = mainTps > 0 ? theme.fg("success", ` | main ${mainTps} t/s`) : "";
|
|
272
|
+
const subagentDetail = subagentTps > 0
|
|
273
|
+
? theme.fg("success", ` | subagents ${subagentTps} t/s (${subagentSummaries.length} call${subagentSummaries.length === 1 ? "" : "s"})`)
|
|
274
|
+
: "";
|
|
275
|
+
|
|
276
|
+
ctx.ui.notify(`${icon} total ${tpsLabel}${mainDetail}${subagentDetail}`, "info");
|
|
277
|
+
const finalMainStatus = mainTps > 0 ? `main ${mainTps} t/s` : "main N/A";
|
|
278
|
+
const finalSubagentStatus = totalSubagentTokens > 0 && totalSubagentMs > 0
|
|
279
|
+
? `subagents ${Math.round(totalSubagentTokens / (totalSubagentMs / 1000))} t/s`
|
|
280
|
+
: "";
|
|
281
|
+
ctx.ui.setStatus(
|
|
282
|
+
"tps",
|
|
283
|
+
`${theme.fg("accent", `done ${tps} t/s`)} ${theme.fg("success", `(${[finalMainStatus, finalSubagentStatus].filter(Boolean).join(" ")})`)}`,
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
@@ -22,12 +22,13 @@ export interface LoadedCounts {
|
|
|
22
22
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
23
23
|
|
|
24
24
|
const PI_LOGO = [
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
25
|
+
"███ ███ █ ███ ███ ███ █",
|
|
26
|
+
"█ █ █ █ █ █ █ █",
|
|
27
|
+
"█ █ █ █ █ █ █ █",
|
|
28
|
+
"███ ███ █ ███ ███ ███ █",
|
|
29
|
+
" █ █ █ █ █ █ █ █",
|
|
30
|
+
" █ █ █ █ █ █ █ █",
|
|
31
|
+
"███ ███ ███ ███ ███ █ █ █",
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
const GRADIENT_COLORS = [
|
|
@@ -89,7 +90,7 @@ interface WelcomeData {
|
|
|
89
90
|
|
|
90
91
|
function buildLeftColumn(data: WelcomeData, colWidth: number): string[] {
|
|
91
92
|
const logoColored = PI_LOGO.map((line) => gradientLine(line));
|
|
92
|
-
|
|
93
|
+
|
|
93
94
|
return [
|
|
94
95
|
"",
|
|
95
96
|
centerText(bold("Welcome back!"), colWidth),
|
|
@@ -104,7 +105,7 @@ function buildLeftColumn(data: WelcomeData, colWidth: number): string[] {
|
|
|
104
105
|
function buildRightColumn(data: WelcomeData, colWidth: number): string[] {
|
|
105
106
|
const hChar = "─";
|
|
106
107
|
const separator = ` ${dim(hChar.repeat(colWidth - 2))}`;
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
// Session lines
|
|
109
110
|
const sessionLines: string[] = [];
|
|
110
111
|
if (data.recentSessions.length === 0) {
|
|
@@ -116,12 +117,12 @@ function buildRightColumn(data: WelcomeData, colWidth: number): string[] {
|
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
// Loaded counts lines
|
|
121
122
|
const countLines: string[] = [];
|
|
122
123
|
const { contextFiles, extensions, skills, promptTemplates } = data.loadedCounts;
|
|
123
124
|
const itemPrefix = dim("- ");
|
|
124
|
-
|
|
125
|
+
|
|
125
126
|
if (contextFiles > 0 || extensions > 0 || skills > 0 || promptTemplates > 0) {
|
|
126
127
|
if (contextFiles > 0) {
|
|
127
128
|
countLines.push(` ${itemPrefix}${fgOnly("gitClean", `${contextFiles}`)} context file${contextFiles !== 1 ? "s" : ""}`);
|
|
@@ -138,7 +139,7 @@ function buildRightColumn(data: WelcomeData, colWidth: number): string[] {
|
|
|
138
139
|
} else {
|
|
139
140
|
countLines.push(` ${dim("No extensions loaded")}`);
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
|
|
142
143
|
return [
|
|
143
144
|
` ${bold(fgOnly("accent", "Tips"))}`,
|
|
144
145
|
` ${dim("/")} for commands`,
|
|
@@ -155,46 +156,46 @@ function buildRightColumn(data: WelcomeData, colWidth: number): string[] {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
function renderWelcomeBox(
|
|
158
|
-
data: WelcomeData,
|
|
159
|
-
termWidth: number,
|
|
159
|
+
data: WelcomeData,
|
|
160
|
+
termWidth: number,
|
|
160
161
|
bottomLine: string,
|
|
161
162
|
): string[] {
|
|
162
163
|
// Minimum width for two-column layout: leftCol(26) + separator(3) + minRightCol(15) = 44
|
|
163
164
|
const minLayoutWidth = 44;
|
|
164
|
-
|
|
165
|
+
|
|
165
166
|
// If terminal is too narrow for the layout, return empty (skip welcome box)
|
|
166
167
|
if (termWidth < minLayoutWidth) {
|
|
167
168
|
return [];
|
|
168
169
|
}
|
|
169
|
-
|
|
170
|
+
|
|
170
171
|
const minWidth = 76;
|
|
171
172
|
const maxWidth = 96;
|
|
172
173
|
// Clamp to termWidth to prevent crash on narrow terminals
|
|
173
174
|
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
174
175
|
const leftCol = 26;
|
|
175
176
|
const rightCol = Math.max(1, boxWidth - leftCol - 3); // Ensure rightCol is at least 1
|
|
176
|
-
|
|
177
|
+
|
|
177
178
|
const hChar = "─";
|
|
178
179
|
const v = dim("│");
|
|
179
180
|
const tl = dim("╭");
|
|
180
181
|
const tr = dim("╮");
|
|
181
182
|
const bl = dim("╰");
|
|
182
183
|
const br = dim("╯");
|
|
183
|
-
|
|
184
|
+
|
|
184
185
|
const leftLines = buildLeftColumn(data, leftCol);
|
|
185
186
|
const rightLines = buildRightColumn(data, rightCol);
|
|
186
|
-
|
|
187
|
+
|
|
187
188
|
const lines: string[] = [];
|
|
188
|
-
|
|
189
|
+
|
|
189
190
|
// Top border with title
|
|
190
|
-
const title = "
|
|
191
|
+
const title = "SelesaiCode";
|
|
191
192
|
const titlePrefix = dim(hChar.repeat(3));
|
|
192
193
|
const titleStyled = titlePrefix + fgOnly("model", title);
|
|
193
194
|
const titleVisLen = 3 + visibleWidth(title);
|
|
194
195
|
const afterTitle = boxWidth - 2 - titleVisLen;
|
|
195
196
|
const afterTitleText = afterTitle > 0 ? dim(hChar.repeat(afterTitle)) : "";
|
|
196
197
|
lines.push(tl + titleStyled + afterTitleText + tr);
|
|
197
|
-
|
|
198
|
+
|
|
198
199
|
// Content rows
|
|
199
200
|
const maxRows = Math.max(leftLines.length, rightLines.length);
|
|
200
201
|
for (let i = 0; i < maxRows; i++) {
|
|
@@ -202,10 +203,10 @@ function renderWelcomeBox(
|
|
|
202
203
|
const right = fitToWidth(rightLines[i] ?? "", rightCol);
|
|
203
204
|
lines.push(v + left + v + right + v);
|
|
204
205
|
}
|
|
205
|
-
|
|
206
|
+
|
|
206
207
|
// Bottom border
|
|
207
208
|
lines.push(bl + bottomLine + br);
|
|
208
|
-
|
|
209
|
+
|
|
209
210
|
return lines;
|
|
210
211
|
}
|
|
211
212
|
|
|
@@ -214,7 +215,7 @@ function renderWelcomeBox(
|
|
|
214
215
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
215
216
|
|
|
216
217
|
/**
|
|
217
|
-
* Welcome overlay component for
|
|
218
|
+
* Welcome overlay component for SelesaiCode.
|
|
218
219
|
* Displays a branded splash screen with logo, tips, and loaded counts.
|
|
219
220
|
*/
|
|
220
221
|
export class WelcomeComponent implements Component {
|
|
@@ -242,12 +243,12 @@ export class WelcomeComponent implements Component {
|
|
|
242
243
|
if (termWidth < minLayoutWidth) {
|
|
243
244
|
return [];
|
|
244
245
|
}
|
|
245
|
-
|
|
246
|
+
|
|
246
247
|
const minWidth = 76;
|
|
247
248
|
const maxWidth = 96;
|
|
248
249
|
// Clamp to termWidth to prevent crash on narrow terminals
|
|
249
250
|
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
250
|
-
|
|
251
|
+
|
|
251
252
|
// Bottom line with countdown
|
|
252
253
|
const countdownText = ` Press any key to continue (${this.countdown}s) `;
|
|
253
254
|
const countdownStyled = dim(countdownText);
|
|
@@ -256,10 +257,10 @@ export class WelcomeComponent implements Component {
|
|
|
256
257
|
const leftPad = Math.floor((bottomContentWidth - countdownVisLen) / 2);
|
|
257
258
|
const rightPad = bottomContentWidth - countdownVisLen - leftPad;
|
|
258
259
|
const hChar = "─";
|
|
259
|
-
const bottomLine = dim(hChar.repeat(Math.max(0, leftPad))) +
|
|
260
|
-
countdownStyled +
|
|
260
|
+
const bottomLine = dim(hChar.repeat(Math.max(0, leftPad))) +
|
|
261
|
+
countdownStyled +
|
|
261
262
|
dim(hChar.repeat(Math.max(0, rightPad)));
|
|
262
|
-
|
|
263
|
+
|
|
263
264
|
return renderWelcomeBox(this.data, termWidth, bottomLine);
|
|
264
265
|
}
|
|
265
266
|
}
|
|
@@ -288,18 +289,18 @@ export class WelcomeHeader implements Component {
|
|
|
288
289
|
if (termWidth < minLayoutWidth) {
|
|
289
290
|
return [];
|
|
290
291
|
}
|
|
291
|
-
|
|
292
|
+
|
|
292
293
|
const minWidth = 76;
|
|
293
294
|
const maxWidth = 96;
|
|
294
295
|
// Clamp to termWidth to prevent crash on narrow terminals
|
|
295
296
|
const boxWidth = Math.min(termWidth, Math.max(minWidth, Math.min(termWidth - 2, maxWidth)));
|
|
296
297
|
const hChar = "─";
|
|
297
|
-
|
|
298
|
+
|
|
298
299
|
// Bottom line with column separator (leftCol=26, rightCol=boxWidth-29)
|
|
299
300
|
const leftCol = 26;
|
|
300
301
|
const rightCol = Math.max(1, boxWidth - leftCol - 3);
|
|
301
302
|
const bottomLine = dim(hChar.repeat(leftCol)) + dim("┴") + dim(hChar.repeat(rightCol));
|
|
302
|
-
|
|
303
|
+
|
|
303
304
|
const lines = renderWelcomeBox(this.data, termWidth, bottomLine);
|
|
304
305
|
if (lines.length > 0) {
|
|
305
306
|
lines.push(""); // Add empty line for spacing only if we rendered content
|
|
@@ -335,7 +336,7 @@ function logDiscoveryError(scope: string, error: unknown): void {
|
|
|
335
336
|
export function discoverLoadedCounts(): LoadedCounts {
|
|
336
337
|
const homeDir = process.env.HOME || process.env.USERPROFILE || osHomedir();
|
|
337
338
|
const cwd = process.cwd();
|
|
338
|
-
|
|
339
|
+
|
|
339
340
|
let contextFiles = 0;
|
|
340
341
|
let extensions = 0;
|
|
341
342
|
let skills = 0;
|
|
@@ -348,7 +349,7 @@ export function discoverLoadedCounts(): LoadedCounts {
|
|
|
348
349
|
join(cwd, ".pi", "AGENTS.md"),
|
|
349
350
|
join(cwd, ".claude", "AGENTS.md"),
|
|
350
351
|
];
|
|
351
|
-
|
|
352
|
+
|
|
352
353
|
for (const path of agentsMdPaths) {
|
|
353
354
|
if (existsSync(path)) contextFiles++;
|
|
354
355
|
}
|
|
@@ -463,9 +464,9 @@ export function discoverLoadedCounts(): LoadedCounts {
|
|
|
463
464
|
join(cwd, ".pi", "skills"),
|
|
464
465
|
join(cwd, "skills"),
|
|
465
466
|
];
|
|
466
|
-
|
|
467
|
+
|
|
467
468
|
const countedSkills = new Set<string>();
|
|
468
|
-
|
|
469
|
+
|
|
469
470
|
for (const dir of skillDirs) {
|
|
470
471
|
if (existsSync(dir)) {
|
|
471
472
|
try {
|
|
@@ -497,9 +498,9 @@ export function discoverLoadedCounts(): LoadedCounts {
|
|
|
497
498
|
join(cwd, ".pi", "commands"),
|
|
498
499
|
join(cwd, ".claude", "commands"),
|
|
499
500
|
];
|
|
500
|
-
|
|
501
|
+
|
|
501
502
|
const countedTemplates = new Set<string>();
|
|
502
|
-
|
|
503
|
+
|
|
503
504
|
function countTemplatesInDir(dir: string) {
|
|
504
505
|
if (!existsSync(dir)) return;
|
|
505
506
|
try {
|
|
@@ -525,7 +526,7 @@ export function discoverLoadedCounts(): LoadedCounts {
|
|
|
525
526
|
logDiscoveryError(`Failed to scan prompt template dir ${dir}`, error);
|
|
526
527
|
}
|
|
527
528
|
}
|
|
528
|
-
|
|
529
|
+
|
|
529
530
|
for (const dir of templateDirs) {
|
|
530
531
|
countTemplatesInDir(dir);
|
|
531
532
|
}
|
|
@@ -538,14 +539,14 @@ export function discoverLoadedCounts(): LoadedCounts {
|
|
|
538
539
|
*/
|
|
539
540
|
export function getRecentSessions(maxCount: number = 3): RecentSession[] {
|
|
540
541
|
const homeDir = process.env.HOME || process.env.USERPROFILE || osHomedir();
|
|
541
|
-
|
|
542
|
+
|
|
542
543
|
const sessionsDirs = [
|
|
543
544
|
join(homeDir, ".pi", "agent", "sessions"),
|
|
544
545
|
join(homeDir, ".pi", "sessions"),
|
|
545
546
|
];
|
|
546
|
-
|
|
547
|
+
|
|
547
548
|
const sessions: { name: string; mtime: number }[] = [];
|
|
548
|
-
|
|
549
|
+
|
|
549
550
|
function scanDir(dir: string) {
|
|
550
551
|
if (!existsSync(dir)) return;
|
|
551
552
|
try {
|
|
@@ -573,15 +574,15 @@ export function getRecentSessions(maxCount: number = 3): RecentSession[] {
|
|
|
573
574
|
logDiscoveryError(`Failed to scan sessions dir ${dir}`, error);
|
|
574
575
|
}
|
|
575
576
|
}
|
|
576
|
-
|
|
577
|
+
|
|
577
578
|
for (const sessionsDir of sessionsDirs) {
|
|
578
579
|
scanDir(sessionsDir);
|
|
579
580
|
}
|
|
580
|
-
|
|
581
|
+
|
|
581
582
|
if (sessions.length === 0) return [];
|
|
582
|
-
|
|
583
|
+
|
|
583
584
|
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
584
|
-
|
|
585
|
+
|
|
585
586
|
const seen = new Set<string>();
|
|
586
587
|
const uniqueSessions: typeof sessions = [];
|
|
587
588
|
for (const s of sessions) {
|