@selesai/code 0.1.19 → 0.2.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.
Files changed (157) hide show
  1. package/dist/agents/architect.md +4 -3
  2. package/dist/agents/builder.md +5 -4
  3. package/dist/agents/commentator.md +3 -2
  4. package/dist/agents/explorer.md +4 -4
  5. package/dist/agents/recapper.md +4 -3
  6. package/dist/config.d.ts +6 -7
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/config.js +7 -9
  9. package/dist/config.js.map +1 -1
  10. package/dist/core/agent-session.d.ts.map +1 -1
  11. package/dist/core/agent-session.js +3 -1
  12. package/dist/core/agent-session.js.map +1 -1
  13. package/dist/core/compaction/compaction.d.ts.map +1 -1
  14. package/dist/core/compaction/compaction.js +18 -24
  15. package/dist/core/compaction/compaction.js.map +1 -1
  16. package/dist/core/extensions/loader.d.ts.map +1 -1
  17. package/dist/core/extensions/loader.js +2 -0
  18. package/dist/core/extensions/loader.js.map +1 -1
  19. package/dist/core/extensions/types.d.ts +8 -1
  20. package/dist/core/extensions/types.d.ts.map +1 -1
  21. package/dist/core/extensions/types.js.map +1 -1
  22. package/dist/core/skills.d.ts.map +1 -1
  23. package/dist/core/skills.js +25 -11
  24. package/dist/core/skills.js.map +1 -1
  25. package/dist/defaults/models.json +143 -39
  26. package/dist/defaults/settings.json +2 -29
  27. package/dist/extensions/package.json +5 -4
  28. package/dist/extensions/pi-intercom/index.ts +12 -5
  29. package/dist/extensions/pi-powerline-footer/index.ts +4 -1
  30. package/dist/extensions/pi-powerline-footer/tps.ts +286 -0
  31. package/dist/extensions/pi-powerline-footer/welcome.ts +47 -46
  32. package/dist/extensions/pi-subagents/README.md +12 -5
  33. package/dist/extensions/pi-subagents/agents/context-builder.md +2 -2
  34. package/dist/extensions/pi-subagents/agents/researcher.md +4 -5
  35. package/dist/extensions/pi-subagents/package.json +8 -5
  36. package/dist/extensions/pi-subagents/prompts/parallel-context-build.md +1 -1
  37. package/dist/extensions/pi-subagents/prompts/parallel-handoff-plan.md +1 -1
  38. package/dist/extensions/pi-subagents/src/agents/agent-management.ts +1 -1
  39. package/dist/extensions/pi-subagents/src/agents/agents.ts +3 -3
  40. package/dist/extensions/pi-subagents/src/extension/control-notices.ts +1 -1
  41. package/dist/extensions/pi-subagents/src/extension/doctor.ts +3 -3
  42. package/dist/extensions/pi-subagents/src/extension/fanout-child.ts +1 -1
  43. package/dist/extensions/pi-subagents/src/extension/index.ts +29 -2
  44. package/dist/extensions/pi-subagents/src/profiles/profiles.ts +2 -2
  45. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +72 -13
  46. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +1 -1
  47. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +1 -1
  48. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +85 -41
  49. package/dist/extensions/pi-subagents/src/runs/foreground/chain-clarify.ts +1 -1
  50. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +1 -1
  51. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +46 -3
  52. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +2 -2
  53. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +1 -3
  54. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +2 -2
  55. package/dist/extensions/pi-subagents/src/runs/shared/pi-args.ts +18 -18
  56. package/dist/extensions/pi-subagents/src/runs/shared/pi-spawn.ts +24 -24
  57. package/dist/extensions/pi-subagents/src/runs/shared/spawn-timing.ts +46 -0
  58. package/dist/extensions/pi-subagents/src/runs/shared/structured-output.ts +2 -2
  59. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -4
  60. package/dist/extensions/pi-subagents/src/shared/fork-context.ts +1 -1
  61. package/dist/extensions/pi-subagents/src/shared/types.ts +6 -6
  62. package/dist/extensions/pi-subagents/src/shared/utils.ts +3 -3
  63. package/dist/extensions/pi-subagents/src/slash/slash-bridge.ts +1 -1
  64. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +1 -1
  65. package/dist/extensions/pi-subagents/src/tui/render-helpers.ts +1 -1
  66. package/dist/extensions/pi-subagents/src/tui/render.ts +1 -1
  67. package/dist/extensions/pi-subagents/test/unit/spawn-timing.test.ts +45 -0
  68. package/dist/extensions/pi-subagents/tsconfig.runner.json +17 -0
  69. package/dist/extensions/pi-web-agent/package.json +31 -0
  70. package/dist/extensions/pi-web-agent/src/backends/config.ts +205 -0
  71. package/dist/extensions/pi-web-agent/src/backends/doctor.ts +136 -0
  72. package/dist/extensions/pi-web-agent/src/backends/factory.ts +152 -0
  73. package/dist/extensions/pi-web-agent/src/backends/settings-reader.ts +25 -0
  74. package/dist/extensions/pi-web-agent/src/cache/ttl-cache.ts +28 -0
  75. package/dist/extensions/pi-web-agent/src/changelog-notice.ts +133 -0
  76. package/dist/extensions/pi-web-agent/src/commands/web-agent-config.ts +946 -0
  77. package/dist/extensions/pi-web-agent/src/extension.ts +126 -0
  78. package/dist/extensions/pi-web-agent/src/extract/readability.ts +118 -0
  79. package/dist/extensions/pi-web-agent/src/fetch/browser-resolution.ts +199 -0
  80. package/dist/extensions/pi-web-agent/src/fetch/firecrawl-fetch.ts +100 -0
  81. package/dist/extensions/pi-web-agent/src/fetch/headless-fetch.ts +117 -0
  82. package/dist/extensions/pi-web-agent/src/fetch/http-fetch.ts +67 -0
  83. package/dist/extensions/pi-web-agent/src/orchestration/answer-synthesizer.ts +60 -0
  84. package/dist/extensions/pi-web-agent/src/orchestration/candidate-selector.ts +50 -0
  85. package/dist/extensions/pi-web-agent/src/orchestration/direct-url.ts +52 -0
  86. package/dist/extensions/pi-web-agent/src/orchestration/evidence-quality.ts +105 -0
  87. package/dist/extensions/pi-web-agent/src/orchestration/evidence-ranker.ts +45 -0
  88. package/dist/extensions/pi-web-agent/src/orchestration/index.ts +28 -0
  89. package/dist/extensions/pi-web-agent/src/orchestration/query-planner.ts +47 -0
  90. package/dist/extensions/pi-web-agent/src/orchestration/research-orchestrator.ts +376 -0
  91. package/dist/extensions/pi-web-agent/src/orchestration/research-types.ts +64 -0
  92. package/dist/extensions/pi-web-agent/src/orchestration/research-worker.ts +181 -0
  93. package/dist/extensions/pi-web-agent/src/orchestration/source-profile.ts +101 -0
  94. package/dist/extensions/pi-web-agent/src/orchestration/stop-decider.ts +81 -0
  95. package/dist/extensions/pi-web-agent/src/presentation/config-store.ts +208 -0
  96. package/dist/extensions/pi-web-agent/src/presentation/config.ts +75 -0
  97. package/dist/extensions/pi-web-agent/src/presentation/explore-presentation.ts +61 -0
  98. package/dist/extensions/pi-web-agent/src/presentation/fetch-presentation.ts +54 -0
  99. package/dist/extensions/pi-web-agent/src/presentation/search-presentation.ts +41 -0
  100. package/dist/extensions/pi-web-agent/src/presentation/select-view.ts +20 -0
  101. package/dist/extensions/pi-web-agent/src/presentation/types.ts +63 -0
  102. package/dist/extensions/pi-web-agent/src/search/brave.ts +114 -0
  103. package/dist/extensions/pi-web-agent/src/search/duckduckgo.ts +72 -0
  104. package/dist/extensions/pi-web-agent/src/search/searxng.ts +96 -0
  105. package/dist/extensions/pi-web-agent/src/tools/web-explore.ts +71 -0
  106. package/dist/extensions/pi-web-agent/src/tools/web-fetch-headless.ts +31 -0
  107. package/dist/extensions/pi-web-agent/src/tools/web-fetch.ts +31 -0
  108. package/dist/extensions/pi-web-agent/src/tools/web-search.ts +157 -0
  109. package/dist/extensions/pi-web-agent/src/types.ts +88 -0
  110. package/dist/extensions/ponytail/index.js +189 -0
  111. package/dist/extensions/ponytail/package.json +8 -0
  112. package/dist/extensions/ponytail/ponytail-config.cjs +122 -0
  113. package/dist/extensions/ponytail/ponytail-instructions.cjs +94 -0
  114. package/dist/extensions/ponytail/test/extension.test.js +167 -0
  115. package/dist/extensions/ponytail/test/helpers.test.js +92 -0
  116. package/dist/extensions/question/constants.ts +38 -0
  117. package/dist/extensions/question/dialog-adapter.ts +74 -0
  118. package/dist/extensions/question/helpers.ts +102 -0
  119. package/dist/extensions/question/index.ts +671 -0
  120. package/dist/extensions/question/navigation.ts +14 -0
  121. package/dist/extensions/question/package.json +18 -0
  122. package/dist/extensions/question/question-list.ts +498 -0
  123. package/dist/extensions/question/row-layout.ts +162 -0
  124. package/dist/extensions/question/schemas.ts +69 -0
  125. package/dist/extensions/question/selection-mode.ts +49 -0
  126. package/dist/extensions/question/shortcuts.ts +53 -0
  127. package/dist/extensions/question/tests/helpers.test.ts +178 -0
  128. package/dist/extensions/question/tests/question-list.test.ts +230 -0
  129. package/dist/extensions/question/tests/row-layout.test.ts +136 -0
  130. package/dist/extensions/question/tests/shortcuts.test.ts +85 -0
  131. package/dist/extensions/question/tests/ui-protocol.test.ts +143 -0
  132. package/dist/extensions/question/tui-adapter.ts +33 -0
  133. package/dist/extensions/question/types.ts +102 -0
  134. package/dist/extensions/question/ui-protocol.ts +46 -0
  135. package/dist/extensions/tokenin-onboarding.ts +265 -2
  136. package/dist/extensions/web-agent-onboarding.ts +222 -0
  137. package/dist/extensions/workflow/adapter.ts +588 -0
  138. package/dist/extensions/workflow/extension.ts +24 -0
  139. package/dist/extensions/workflow/modes/prototype.ts +156 -0
  140. package/dist/extensions/workflow/modes/quick.ts +138 -0
  141. package/dist/extensions/workflow/package.json +17 -0
  142. package/dist/extensions/workflow/state-machine.ts +386 -0
  143. package/dist/index.d.ts +1 -1
  144. package/dist/index.d.ts.map +1 -1
  145. package/dist/index.js +1 -1
  146. package/dist/index.js.map +1 -1
  147. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  148. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  149. package/dist/modes/interactive/interactive-mode.js +57 -13
  150. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  151. package/dist/utils/mime.d.ts.map +1 -1
  152. package/dist/utils/mime.js +24 -1
  153. package/dist/utils/mime.js.map +1 -1
  154. package/docs/docs.json +4 -0
  155. package/docs/workflows.md +206 -0
  156. package/package.json +5 -1
  157. package/README.md +0 -198
@@ -1,86 +1,190 @@
1
1
  {
2
2
  "providers": {
3
3
  "tokenin": {
4
- "api": "openai-responses",
4
+ "api": "openai-completions",
5
5
  "apiKey": "sk-xxx",
6
6
  "baseUrl": "https://lite.andlet.me/v1",
7
+ "authHeader": true,
8
+ "compat": {
9
+ "supportsDeveloperRole": false,
10
+ "supportsUsageInStreaming": true,
11
+ "maxTokensField": "max_tokens"
12
+ },
7
13
  "models": [
8
14
  {
9
15
  "id": "kimi-k2.7-code",
10
- "name": "Kimi K2.7-code",
16
+ "name": "Kimi K2.7 Code",
11
17
  "reasoning": true,
12
18
  "input": ["text", "image"],
13
- "contextWindow": 256000,
14
- "maxTokens": 65536,
19
+ "contextWindow": 262128,
15
20
  "cost": {
16
21
  "input": 0.95,
17
22
  "output": 4,
18
- "cacheRead": 0.16,
23
+ "cacheRead": 0.2375,
19
24
  "cacheWrite": 0
25
+ },
26
+ "compat": {
27
+ "supportsReasoningEffort": false
20
28
  }
21
29
  },
22
30
  {
23
- "id": "glm-5.2",
24
- "name": "GLM 5.2",
31
+ "id": "kimi-k2.6",
32
+ "name": "Kimi K2.6",
25
33
  "reasoning": true,
26
- "input": ["text"],
27
- "contextWindow": 976000,
28
- "maxTokens": 65536,
34
+ "input": ["text", "image"],
35
+ "contextWindow": 262128,
36
+ "cost": {
37
+ "input": 0.69,
38
+ "output": 3.22,
39
+ "cacheRead": 0.1725,
40
+ "cacheWrite": 0
41
+ },
42
+ "compat": {
43
+ "supportsReasoningEffort": false
44
+ }
45
+ },
46
+ {
47
+ "id": "kimi-k2.6-fast",
48
+ "name": "Kimi K2.6 Fast",
49
+ "reasoning": false,
50
+ "input": ["text", "image"],
51
+ "contextWindow": 262128,
29
52
  "cost": {
30
- "input": 1.4,
31
- "output": 4.4,
32
- "cacheRead": 0.26,
53
+ "input": 0.69,
54
+ "output": 3.22,
55
+ "cacheRead": 0.1725,
33
56
  "cacheWrite": 0
57
+ },
58
+ "compat": {
59
+ "supportsReasoningEffort": false
34
60
  }
35
61
  },
36
62
  {
37
- "id": "deepseek-v4-flash",
38
- "name": "DeepSeek V4 Flash",
63
+ "id": "qwen3.5-397b",
64
+ "name": "Qwen3.5 397B",
39
65
  "reasoning": true,
40
66
  "input": ["text"],
41
- "contextWindow": 1000000,
42
- "maxTokens": 384000,
67
+ "contextWindow": 262128,
43
68
  "cost": {
44
- "input": 0.14,
45
- "output": 0.28,
46
- "cacheRead": 0.0028,
69
+ "input": 0.69,
70
+ "output": 4.14,
71
+ "cacheRead": 0.1725,
47
72
  "cacheWrite": 0
48
73
  },
49
74
  "compat": {
50
- "requiresReasoningContentOnAssistantMessages": true,
51
- "thinkingFormat": "deepseek"
75
+ "supportsReasoningEffort": false
52
76
  }
53
77
  },
54
78
  {
55
- "id": "deepseek-v4-pro",
56
- "name": "DeepSeek V4 Pro",
57
- "reasoning": true,
79
+ "id": "qwen3.5-397b-fast",
80
+ "name": "Qwen3.5 397B Fast",
81
+ "reasoning": false,
58
82
  "input": ["text"],
59
- "contextWindow": 1000000,
60
- "maxTokens": 384000,
83
+ "contextWindow": 262128,
61
84
  "cost": {
62
- "input": 1.74,
63
- "output": 3.48,
64
- "cacheRead": 0.0145,
85
+ "input": 0.69,
86
+ "output": 4.14,
87
+ "cacheRead": 0.1725,
65
88
  "cacheWrite": 0
66
89
  },
67
90
  "compat": {
68
- "requiresReasoningContentOnAssistantMessages": true,
69
- "thinkingFormat": "deepseek"
91
+ "supportsReasoningEffort": false
70
92
  }
71
93
  },
72
94
  {
73
- "id": "minimax-m3",
74
- "name": "MiniMax M3",
95
+ "id": "qwen3.6-35b",
96
+ "name": "Qwen3.6 35B",
75
97
  "reasoning": true,
76
98
  "input": ["text", "image"],
77
- "contextWindow": 512000,
78
- "maxTokens": 131072,
99
+ "contextWindow": 131056,
79
100
  "cost": {
80
- "input": 0.6,
81
- "output": 2.4,
82
- "cacheRead": 0.12,
101
+ "input": 0.29,
102
+ "output": 1.15,
103
+ "cacheRead": 0.0725,
83
104
  "cacheWrite": 0
105
+ },
106
+ "compat": {
107
+ "supportsReasoningEffort": false
108
+ }
109
+ },
110
+ {
111
+ "id": "qwen3.6-35b-fast",
112
+ "name": "Qwen3.6 35B Fast",
113
+ "reasoning": false,
114
+ "input": ["text", "image"],
115
+ "contextWindow": 131056,
116
+ "cost": {
117
+ "input": 0.29,
118
+ "output": 1.15,
119
+ "cacheRead": 0.0725,
120
+ "cacheWrite": 0
121
+ },
122
+ "compat": {
123
+ "supportsReasoningEffort": false
124
+ }
125
+ },
126
+ {
127
+ "id": "glm-5.2",
128
+ "name": "GLM-5.2",
129
+ "reasoning": true,
130
+ "input": ["text"],
131
+ "contextWindow": 1048560,
132
+ "cost": {
133
+ "input": 1.45,
134
+ "output": 4.5,
135
+ "cacheRead": 0.3625,
136
+ "cacheWrite": 0
137
+ },
138
+ "compat": {
139
+ "supportsReasoningEffort": true
140
+ }
141
+ },
142
+ {
143
+ "id": "glm-5.2-fast",
144
+ "name": "GLM-5.2 Fast",
145
+ "reasoning": false,
146
+ "input": ["text"],
147
+ "contextWindow": 1048560,
148
+ "cost": {
149
+ "input": 1.45,
150
+ "output": 4.5,
151
+ "cacheRead": 0.3625,
152
+ "cacheWrite": 0
153
+ },
154
+ "compat": {
155
+ "supportsReasoningEffort": true
156
+ }
157
+ },
158
+ {
159
+ "id": "glm-5.2-short",
160
+ "name": "GLM-5.2 Short",
161
+ "reasoning": true,
162
+ "input": ["text"],
163
+ "contextWindow": 199984,
164
+ "cost": {
165
+ "input": 1.45,
166
+ "output": 4.5,
167
+ "cacheRead": 0.3625,
168
+ "cacheWrite": 0
169
+ },
170
+ "compat": {
171
+ "supportsReasoningEffort": true
172
+ }
173
+ },
174
+ {
175
+ "id": "glm-5.2-short-fast",
176
+ "name": "GLM-5.2 Short Fast",
177
+ "reasoning": false,
178
+ "input": ["text"],
179
+ "contextWindow": 199984,
180
+ "cost": {
181
+ "input": 1.45,
182
+ "output": 4.5,
183
+ "cacheRead": 0.3625,
184
+ "cacheWrite": 0
185
+ },
186
+ "compat": {
187
+ "supportsReasoningEffort": true
84
188
  }
85
189
  }
86
190
  ]
@@ -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
- "./question.ts",
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
- "./prototype.ts",
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 = "PI_SUBAGENT_ORCHESTRATOR_TARGET";
21
- const SUBAGENT_RUN_ID_ENV = "PI_SUBAGENT_RUN_ID";
22
- const SUBAGENT_CHILD_AGENT_ENV = "PI_SUBAGENT_CHILD_AGENT";
23
- const SUBAGENT_CHILD_INDEX_ENV = "PI_SUBAGENT_CHILD_INDEX";
24
- const SUBAGENT_INTERCOM_SESSION_NAME_ENV = "PI_SUBAGENT_INTERCOM_SESSION_NAME";
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
+ }