@tt-a1i/openpi 0.3.1 → 0.5.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/README.md +184 -59
- package/SETUP.md +23 -7
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +145 -0
- package/extensions/ask-user/index.ts +30 -14
- package/extensions/background-terminals/index.ts +30 -2
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +486 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +14 -6
- package/extensions/background-terminals/src/result-delivery.ts +4 -1
- package/extensions/background-terminals/src/ui/ps.ts +132 -129
- package/extensions/capabilities/index.ts +30 -42
- package/extensions/capabilities/src/ui.ts +93 -0
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/schedule.ts +7 -1
- package/extensions/file-mutation-display/index.ts +34 -76
- package/extensions/file-mutation-display/render.ts +146 -87
- package/extensions/file-search/index.ts +8 -7
- package/extensions/file-search/src/binaries.ts +75 -59
- package/extensions/git-info/src/changed-files-view.ts +47 -14
- package/extensions/git-read/index.ts +328 -0
- package/extensions/git-read/src/args.ts +171 -0
- package/extensions/git-read/src/process.ts +81 -0
- package/extensions/git-read/src/prompt.ts +56 -0
- package/extensions/model-info/index.ts +21 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +7 -2
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +222 -140
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +127 -131
- package/extensions/shared/activity-status.ts +36 -5
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/below-editor-navigation.ts +26 -0
- package/extensions/shared/capability-intent.ts +53 -0
- package/extensions/shared/child-session.ts +444 -22
- package/extensions/shared/result-budget.ts +134 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/screen-chrome.ts +133 -0
- package/extensions/shared/setup-config.ts +97 -38
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/spinner.ts +28 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +113 -0
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +42 -8
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +461 -186
- package/extensions/subagents/navigation.ts +86 -28
- package/extensions/subagents/src/agent-types.ts +37 -15
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +375 -66
- package/extensions/subagents/src/domain.ts +5 -0
- package/extensions/subagents/src/id-sequence.ts +84 -0
- package/extensions/subagents/src/manager.ts +651 -536
- package/extensions/subagents/src/prompt.ts +185 -42
- package/extensions/subagents/src/result-artifact.ts +146 -0
- package/extensions/subagents/src/result-delivery.ts +7 -1
- package/extensions/subagents/src/runtime.ts +23 -6
- package/extensions/subagents/src/ui/takeover.ts +128 -337
- package/extensions/subagents/src/ui/transcript.ts +38 -501
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/tasks/ui.ts +79 -62
- package/extensions/ui-customization/footer.ts +7 -44
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +185 -0
- package/extensions/web/index.ts +234 -0
- package/extensions/workflows/artifacts.ts +147 -22
- package/extensions/workflows/completion-projection.ts +457 -0
- package/extensions/workflows/controller.ts +14 -2
- package/extensions/workflows/coordinator.ts +62 -0
- package/extensions/workflows/dashboard.ts +458 -339
- package/extensions/workflows/handoff.ts +121 -25
- package/extensions/workflows/index.ts +1042 -492
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +131 -19
- package/extensions/workflows/navigation.ts +61 -18
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +166 -10
- package/extensions/workflows/replay-safety.ts +58 -27
- package/extensions/workflows/result-delivery.ts +253 -0
- package/extensions/workflows/retention.ts +593 -0
- package/extensions/workflows/runner.ts +388 -279
- package/extensions/workflows/sandbox-child.cjs +36 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +28 -8
- package/skills/subagents/REFERENCE.md +189 -0
- package/skills/subagents/SKILL.md +2 -2
- package/skills/workflows/REFERENCE.md +10 -5
- package/skills/workflows/SKILL.md +53 -10
- package/web/adapter/pi-adapter.ts +661 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +789 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +462 -0
- package/web/runtime/pi-runtime.ts +991 -0
- package/web/runtime/types.ts +71 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1398 -0
- package/web/ui/index.html +139 -0
- package/web/ui/styles.css +598 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -296
- package/extensions/subagents/src/format.ts +0 -48
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
export type WebCapabilityKind =
|
|
2
|
+
| "subagents"
|
|
3
|
+
| "workflows"
|
|
4
|
+
| "background-terminals";
|
|
5
|
+
|
|
6
|
+
export const WEB_MAX_CAPABILITY_ITEMS = 32;
|
|
7
|
+
const WEB_MAX_ACTIVITY_TEXT = 160;
|
|
8
|
+
const WEB_MAX_WORKFLOW_AGENTS_SCANNED = 1_024;
|
|
9
|
+
|
|
10
|
+
export interface WebSubagentActivity {
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly title: string;
|
|
13
|
+
readonly status: "running" | "done" | "error";
|
|
14
|
+
readonly outcome?: "completed" | "failed" | "interrupted";
|
|
15
|
+
readonly createdAt: number;
|
|
16
|
+
readonly settledAt?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface WebWorkflowActivity {
|
|
20
|
+
readonly runId: string;
|
|
21
|
+
readonly name?: string;
|
|
22
|
+
readonly status: "running" | "completed" | "failed" | "aborted" | "uncertain";
|
|
23
|
+
readonly currentPhase?: string;
|
|
24
|
+
readonly startedAt: number;
|
|
25
|
+
readonly finishedAt?: number;
|
|
26
|
+
readonly agents: {
|
|
27
|
+
readonly total: number;
|
|
28
|
+
readonly running: number;
|
|
29
|
+
readonly done: number;
|
|
30
|
+
readonly error: number;
|
|
31
|
+
readonly uncertain: number;
|
|
32
|
+
readonly omitted?: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface WebBackgroundTerminalActivity {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly title: string;
|
|
39
|
+
readonly status: "running" | "done" | "failed" | "killed" | "timed_out";
|
|
40
|
+
readonly createdAt: number;
|
|
41
|
+
readonly settledAt?: number;
|
|
42
|
+
readonly exitCode?: number;
|
|
43
|
+
readonly signal?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface WebCapabilityProjection<
|
|
47
|
+
Item =
|
|
48
|
+
| WebSubagentActivity
|
|
49
|
+
| WebWorkflowActivity
|
|
50
|
+
| WebBackgroundTerminalActivity,
|
|
51
|
+
> {
|
|
52
|
+
readonly items: readonly Item[];
|
|
53
|
+
readonly omitted: number;
|
|
54
|
+
/** Records were omitted or a user-visible string was shortened. */
|
|
55
|
+
readonly truncated: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface WebCapabilitySnapshot {
|
|
59
|
+
readonly subagents?: WebCapabilityProjection<WebSubagentActivity>;
|
|
60
|
+
readonly workflows?: WebCapabilityProjection<WebWorkflowActivity>;
|
|
61
|
+
readonly "background-terminals"?: WebCapabilityProjection<WebBackgroundTerminalActivity>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface WebCapabilityProvider {
|
|
65
|
+
readonly kind: WebCapabilityKind;
|
|
66
|
+
readonly snapshot: () => WebCapabilityProjection;
|
|
67
|
+
readonly subscribe?: (listener: () => void) => () => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
interface BoundedActivityText {
|
|
71
|
+
readonly value: string;
|
|
72
|
+
readonly truncated: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function boundedActivityText(value: string): BoundedActivityText {
|
|
76
|
+
if (value.length <= WEB_MAX_ACTIVITY_TEXT) {
|
|
77
|
+
return { value, truncated: false };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
value: `${value.slice(0, WEB_MAX_ACTIVITY_TEXT - 1)}…`,
|
|
81
|
+
truncated: true,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function newestActivityAt(value: {
|
|
86
|
+
readonly createdAt?: number;
|
|
87
|
+
readonly startedAt?: number;
|
|
88
|
+
readonly settledAt?: number;
|
|
89
|
+
readonly finishedAt?: number;
|
|
90
|
+
}) {
|
|
91
|
+
return (
|
|
92
|
+
value.settledAt ??
|
|
93
|
+
value.finishedAt ??
|
|
94
|
+
value.createdAt ??
|
|
95
|
+
value.startedAt ??
|
|
96
|
+
0
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function boundedActivityProjection<
|
|
101
|
+
Source extends {
|
|
102
|
+
readonly status: string;
|
|
103
|
+
readonly createdAt?: number;
|
|
104
|
+
readonly startedAt?: number;
|
|
105
|
+
readonly settledAt?: number;
|
|
106
|
+
readonly finishedAt?: number;
|
|
107
|
+
},
|
|
108
|
+
Item,
|
|
109
|
+
>(
|
|
110
|
+
source: readonly Source[],
|
|
111
|
+
project: (value: Source) => {
|
|
112
|
+
readonly item: Item;
|
|
113
|
+
readonly truncated: boolean;
|
|
114
|
+
},
|
|
115
|
+
) {
|
|
116
|
+
const selected: Source[] = [];
|
|
117
|
+
const compare = (left: Source, right: Source) => {
|
|
118
|
+
const running =
|
|
119
|
+
Number(right.status === "running") - Number(left.status === "running");
|
|
120
|
+
return running || newestActivityAt(right) - newestActivityAt(left);
|
|
121
|
+
};
|
|
122
|
+
for (const value of source) {
|
|
123
|
+
const insertAt = selected.findIndex(
|
|
124
|
+
(current) => compare(value, current) < 0,
|
|
125
|
+
);
|
|
126
|
+
if (insertAt < 0) selected.push(value);
|
|
127
|
+
else selected.splice(insertAt, 0, value);
|
|
128
|
+
if (selected.length > WEB_MAX_CAPABILITY_ITEMS) selected.pop();
|
|
129
|
+
}
|
|
130
|
+
const projected = selected.map(project);
|
|
131
|
+
const omitted = Math.max(0, source.length - projected.length);
|
|
132
|
+
return {
|
|
133
|
+
items: projected.map(({ item }) => item),
|
|
134
|
+
omitted,
|
|
135
|
+
truncated: omitted > 0 || projected.some((value) => value.truncated),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function projectSubagentCapability(
|
|
140
|
+
source: readonly {
|
|
141
|
+
readonly id: string;
|
|
142
|
+
readonly title: string;
|
|
143
|
+
readonly status: WebSubagentActivity["status"];
|
|
144
|
+
readonly outcome?: WebSubagentActivity["outcome"];
|
|
145
|
+
readonly createdAt: number;
|
|
146
|
+
readonly settledAt?: number;
|
|
147
|
+
}[],
|
|
148
|
+
): WebCapabilityProjection<WebSubagentActivity> {
|
|
149
|
+
return boundedActivityProjection(source, (value) => {
|
|
150
|
+
const id = boundedActivityText(value.id);
|
|
151
|
+
const title = boundedActivityText(value.title);
|
|
152
|
+
return {
|
|
153
|
+
item: {
|
|
154
|
+
id: id.value,
|
|
155
|
+
title: title.value,
|
|
156
|
+
status: value.status,
|
|
157
|
+
...(value.outcome ? { outcome: value.outcome } : {}),
|
|
158
|
+
createdAt: value.createdAt,
|
|
159
|
+
...(value.settledAt !== undefined
|
|
160
|
+
? { settledAt: value.settledAt }
|
|
161
|
+
: {}),
|
|
162
|
+
},
|
|
163
|
+
truncated: id.truncated || title.truncated,
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function projectWorkflowCapability(
|
|
169
|
+
source: readonly {
|
|
170
|
+
readonly runId: string;
|
|
171
|
+
readonly name?: string;
|
|
172
|
+
readonly status: WebWorkflowActivity["status"];
|
|
173
|
+
readonly currentPhase?: string;
|
|
174
|
+
readonly startedAt: number;
|
|
175
|
+
readonly finishedAt?: number;
|
|
176
|
+
readonly agents: readonly {
|
|
177
|
+
readonly state: keyof WebWorkflowActivity["agents"] | string;
|
|
178
|
+
}[];
|
|
179
|
+
}[],
|
|
180
|
+
): WebCapabilityProjection<WebWorkflowActivity> {
|
|
181
|
+
return boundedActivityProjection(source, (value) => {
|
|
182
|
+
const runId = boundedActivityText(value.runId);
|
|
183
|
+
const name = value.name ? boundedActivityText(value.name) : undefined;
|
|
184
|
+
const phase = value.currentPhase
|
|
185
|
+
? boundedActivityText(value.currentPhase)
|
|
186
|
+
: undefined;
|
|
187
|
+
const counts = {
|
|
188
|
+
total: value.agents.length,
|
|
189
|
+
running: 0,
|
|
190
|
+
done: 0,
|
|
191
|
+
error: 0,
|
|
192
|
+
uncertain: 0,
|
|
193
|
+
};
|
|
194
|
+
const scannedAgents = value.agents.slice(
|
|
195
|
+
0,
|
|
196
|
+
WEB_MAX_WORKFLOW_AGENTS_SCANNED,
|
|
197
|
+
);
|
|
198
|
+
for (const agent of scannedAgents) {
|
|
199
|
+
if (agent.state === "running") counts.running++;
|
|
200
|
+
else if (agent.state === "done") counts.done++;
|
|
201
|
+
else if (agent.state === "error") counts.error++;
|
|
202
|
+
else counts.uncertain++;
|
|
203
|
+
}
|
|
204
|
+
const agentsOmitted = value.agents.length - scannedAgents.length;
|
|
205
|
+
return {
|
|
206
|
+
item: {
|
|
207
|
+
runId: runId.value,
|
|
208
|
+
...(name ? { name: name.value } : {}),
|
|
209
|
+
status: value.status,
|
|
210
|
+
...(phase ? { currentPhase: phase.value } : {}),
|
|
211
|
+
startedAt: value.startedAt,
|
|
212
|
+
...(value.finishedAt !== undefined
|
|
213
|
+
? { finishedAt: value.finishedAt }
|
|
214
|
+
: {}),
|
|
215
|
+
agents: {
|
|
216
|
+
...counts,
|
|
217
|
+
...(agentsOmitted > 0 ? { omitted: agentsOmitted } : {}),
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
truncated:
|
|
221
|
+
runId.truncated ||
|
|
222
|
+
name?.truncated === true ||
|
|
223
|
+
phase?.truncated === true ||
|
|
224
|
+
agentsOmitted > 0,
|
|
225
|
+
};
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function projectBackgroundTerminalCapability(
|
|
230
|
+
source: readonly {
|
|
231
|
+
readonly id: string;
|
|
232
|
+
readonly title: string;
|
|
233
|
+
readonly status: WebBackgroundTerminalActivity["status"];
|
|
234
|
+
readonly createdAt: number;
|
|
235
|
+
readonly settledAt?: number;
|
|
236
|
+
readonly exitCode?: number;
|
|
237
|
+
readonly signal?: string;
|
|
238
|
+
}[],
|
|
239
|
+
): WebCapabilityProjection<WebBackgroundTerminalActivity> {
|
|
240
|
+
return boundedActivityProjection(source, (value) => {
|
|
241
|
+
const id = boundedActivityText(value.id);
|
|
242
|
+
const title = boundedActivityText(value.title);
|
|
243
|
+
const signal = value.signal ? boundedActivityText(value.signal) : undefined;
|
|
244
|
+
return {
|
|
245
|
+
item: {
|
|
246
|
+
id: id.value,
|
|
247
|
+
title: title.value,
|
|
248
|
+
status: value.status,
|
|
249
|
+
createdAt: value.createdAt,
|
|
250
|
+
...(value.settledAt !== undefined
|
|
251
|
+
? { settledAt: value.settledAt }
|
|
252
|
+
: {}),
|
|
253
|
+
...(value.exitCode !== undefined ? { exitCode: value.exitCode } : {}),
|
|
254
|
+
...(signal ? { signal: signal.value } : {}),
|
|
255
|
+
},
|
|
256
|
+
truncated: id.truncated || title.truncated || signal?.truncated === true,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** The Pi SessionManager object itself is the capability-lifetime identity. */
|
|
262
|
+
export type WebCapabilityScope = object;
|
|
263
|
+
|
|
264
|
+
type CapabilityListener = (scope: WebCapabilityScope) => void;
|
|
265
|
+
type ProviderSubscriptions = Map<
|
|
266
|
+
WebCapabilityScope,
|
|
267
|
+
Map<WebCapabilityKind, () => void>
|
|
268
|
+
>;
|
|
269
|
+
|
|
270
|
+
interface WebObserverRegistry {
|
|
271
|
+
readonly version: 1;
|
|
272
|
+
readonly providers: Map<
|
|
273
|
+
WebCapabilityScope,
|
|
274
|
+
Map<WebCapabilityKind, WebCapabilityProvider>
|
|
275
|
+
>;
|
|
276
|
+
readonly listeners: Map<CapabilityListener, ProviderSubscriptions>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const WEB_OBSERVER_REGISTRY_KEY = Symbol.for(
|
|
280
|
+
"@tt-a1i/openpi/web-observer-registry/v1",
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
function sharedWebObserverRegistry(): WebObserverRegistry {
|
|
284
|
+
const existing: unknown = Reflect.get(globalThis, WEB_OBSERVER_REGISTRY_KEY);
|
|
285
|
+
if (existing !== undefined) {
|
|
286
|
+
if (
|
|
287
|
+
!existing ||
|
|
288
|
+
typeof existing !== "object" ||
|
|
289
|
+
(existing as { version?: unknown }).version !== 1 ||
|
|
290
|
+
!((existing as { providers?: unknown }).providers instanceof Map) ||
|
|
291
|
+
!((existing as { listeners?: unknown }).listeners instanceof Map)
|
|
292
|
+
) {
|
|
293
|
+
throw new Error("Incompatible OpenPI Web observer registry");
|
|
294
|
+
}
|
|
295
|
+
return existing as WebObserverRegistry;
|
|
296
|
+
}
|
|
297
|
+
const registry: WebObserverRegistry = {
|
|
298
|
+
version: 1,
|
|
299
|
+
providers: new Map(),
|
|
300
|
+
listeners: new Map(),
|
|
301
|
+
};
|
|
302
|
+
Object.defineProperty(globalThis, WEB_OBSERVER_REGISTRY_KEY, {
|
|
303
|
+
value: registry,
|
|
304
|
+
configurable: false,
|
|
305
|
+
enumerable: false,
|
|
306
|
+
writable: false,
|
|
307
|
+
});
|
|
308
|
+
return registry;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Pi may load package extensions from a managed install while the standalone
|
|
312
|
+
// CLI runs from a global npm install. Symbol.for keeps those same-process module
|
|
313
|
+
// copies on one versioned registry without adding a second persistence layer.
|
|
314
|
+
const { providers, listeners } = sharedWebObserverRegistry();
|
|
315
|
+
|
|
316
|
+
function connect(
|
|
317
|
+
scope: WebCapabilityScope,
|
|
318
|
+
provider: WebCapabilityProvider,
|
|
319
|
+
listener: CapabilityListener,
|
|
320
|
+
) {
|
|
321
|
+
const subscriptions = listeners.get(listener);
|
|
322
|
+
let scopedSubscriptions = subscriptions?.get(scope);
|
|
323
|
+
if (!scopedSubscriptions && subscriptions) {
|
|
324
|
+
scopedSubscriptions = new Map();
|
|
325
|
+
subscriptions.set(scope, scopedSubscriptions);
|
|
326
|
+
}
|
|
327
|
+
scopedSubscriptions?.get(provider.kind)?.();
|
|
328
|
+
const unsubscribe = provider.subscribe?.(() => listener(scope));
|
|
329
|
+
if (unsubscribe) scopedSubscriptions?.set(provider.kind, unsubscribe);
|
|
330
|
+
else scopedSubscriptions?.delete(provider.kind);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export function registerWebCapability(
|
|
334
|
+
scope: WebCapabilityScope,
|
|
335
|
+
provider: WebCapabilityProvider,
|
|
336
|
+
) {
|
|
337
|
+
let scopedProviders = providers.get(scope);
|
|
338
|
+
if (!scopedProviders) {
|
|
339
|
+
scopedProviders = new Map();
|
|
340
|
+
providers.set(scope, scopedProviders);
|
|
341
|
+
}
|
|
342
|
+
scopedProviders.set(provider.kind, provider);
|
|
343
|
+
for (const listener of listeners.keys()) {
|
|
344
|
+
connect(scope, provider, listener);
|
|
345
|
+
listener(scope);
|
|
346
|
+
}
|
|
347
|
+
return () => {
|
|
348
|
+
const currentProviders = providers.get(scope);
|
|
349
|
+
if (currentProviders?.get(provider.kind) !== provider) return;
|
|
350
|
+
currentProviders.delete(provider.kind);
|
|
351
|
+
if (currentProviders.size === 0) providers.delete(scope);
|
|
352
|
+
for (const [listener, subscriptions] of listeners) {
|
|
353
|
+
const scopedSubscriptions = subscriptions.get(scope);
|
|
354
|
+
scopedSubscriptions?.get(provider.kind)?.();
|
|
355
|
+
scopedSubscriptions?.delete(provider.kind);
|
|
356
|
+
if (scopedSubscriptions?.size === 0) subscriptions.delete(scope);
|
|
357
|
+
listener(scope);
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function subscribeWebCapabilities(listener: CapabilityListener) {
|
|
363
|
+
const subscriptions: ProviderSubscriptions = new Map();
|
|
364
|
+
listeners.set(listener, subscriptions);
|
|
365
|
+
for (const [scope, scopedProviders] of providers) {
|
|
366
|
+
for (const provider of scopedProviders.values()) {
|
|
367
|
+
connect(scope, provider, listener);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return () => {
|
|
371
|
+
listeners.delete(listener);
|
|
372
|
+
for (const scopedSubscriptions of subscriptions.values()) {
|
|
373
|
+
for (const unsubscribe of scopedSubscriptions.values()) unsubscribe();
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export function webCapabilitySnapshot(
|
|
379
|
+
scope: WebCapabilityScope,
|
|
380
|
+
): WebCapabilitySnapshot {
|
|
381
|
+
const scopedProviders = providers.get(scope);
|
|
382
|
+
if (!scopedProviders) return {};
|
|
383
|
+
return Object.fromEntries(
|
|
384
|
+
[...scopedProviders].map(([kind, provider]) => [kind, provider.snapshot()]),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export function notifyWebCapabilities(scope: WebCapabilityScope) {
|
|
389
|
+
for (const listener of listeners.keys()) listener(scope);
|
|
390
|
+
}
|
|
@@ -292,6 +292,17 @@ export interface WorktreeCleanup {
|
|
|
292
292
|
readonly ignored?: boolean;
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
+
export function formatWorktreeCleanupWarning(
|
|
296
|
+
cleanup: WorktreeCleanup,
|
|
297
|
+
path: string,
|
|
298
|
+
): string | undefined {
|
|
299
|
+
if (cleanup.removed && cleanup.branchDeleted) return undefined;
|
|
300
|
+
const location = cleanup.removed
|
|
301
|
+
? `branch ${cleanup.branch} remains`
|
|
302
|
+
: `checkout was not confirmed removed; inspect ${path}`;
|
|
303
|
+
return cleanup.reason ? `${cleanup.reason}; ${location}` : location;
|
|
304
|
+
}
|
|
305
|
+
|
|
295
306
|
export interface WorktreeCommitCount {
|
|
296
307
|
readonly ok: boolean;
|
|
297
308
|
readonly count?: number;
|