pi-orchestrator-config 4.1.5
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 +808 -0
- package/agents/api-documenter.md +32 -0
- package/agents/bash-expert.md +53 -0
- package/agents/code-reviewer-docs.md +145 -0
- package/agents/code-reviewer-guidelines.md +97 -0
- package/agents/code-reviewer-quality.md +142 -0
- package/agents/code-reviewer-security.md +107 -0
- package/agents/code-reviewer-spec.md +164 -0
- package/agents/debugger.md +63 -0
- package/agents/docker-expert.md +40 -0
- package/agents/docs-fetcher.md +48 -0
- package/agents/git-expert.md +63 -0
- package/agents/github-expert.md +83 -0
- package/agents/go-expert.md +62 -0
- package/agents/issue-reviewer-feasibility.md +60 -0
- package/agents/issue-reviewer-scope.md +63 -0
- package/agents/issue-reviewer-spec.md +77 -0
- package/agents/java-expert.md +31 -0
- package/agents/jenkins-expert.md +38 -0
- package/agents/kubernetes-expert.md +40 -0
- package/agents/planner.md +42 -0
- package/agents/python-expert.md +61 -0
- package/agents/reviewer.md +26 -0
- package/agents/scout.md +40 -0
- package/agents/security-auditor.md +254 -0
- package/agents/technical-documentation-writer.md +37 -0
- package/agents/test-automator.md +55 -0
- package/agents/test-runner.md +47 -0
- package/agents/ts-expert.md +23 -0
- package/agents/worker.md +30 -0
- package/extensions/acpx-provider/index.ts +104 -0
- package/extensions/acpx-provider/load-runtime.ts +99 -0
- package/extensions/acpx-provider/runtime-models.ts +40 -0
- package/extensions/cli-provider/agents/claude.ts +128 -0
- package/extensions/cli-provider/agents/cursor.ts +84 -0
- package/extensions/cli-provider/agents/gemini.ts +100 -0
- package/extensions/cli-provider/discover.ts +77 -0
- package/extensions/cli-provider/index.ts +57 -0
- package/extensions/cli-provider/parsers.ts +304 -0
- package/extensions/cli-provider/providers.ts +58 -0
- package/extensions/cli-provider/runner.ts +225 -0
- package/extensions/cli-provider/runtime-models.ts +32 -0
- package/extensions/cli-provider/session-reaper.ts +122 -0
- package/extensions/cli-provider/sessions.ts +533 -0
- package/extensions/cli-provider/shared/discover-cache.ts +235 -0
- package/extensions/cli-provider/types.ts +48 -0
- package/extensions/coms/coms-net-server.ts +1642 -0
- package/extensions/coms/coms-net-wrapper.ts +521 -0
- package/extensions/coms/coms-net.ts +1589 -0
- package/extensions/coms/coms-p2p.ts +1879 -0
- package/extensions/coms/coms-shared.ts +510 -0
- package/extensions/coms/coms-wrapper.ts +138 -0
- package/extensions/coms/index.ts +20 -0
- package/extensions/coms/themeMap.ts +147 -0
- package/extensions/image-gen/image-gen.ts +332 -0
- package/extensions/image-gen/index.ts +14 -0
- package/extensions/orchestrator/agents.ts +134 -0
- package/extensions/orchestrator/ask-user.ts +293 -0
- package/extensions/orchestrator/async-agents.ts +1037 -0
- package/extensions/orchestrator/async-capability.ts +129 -0
- package/extensions/orchestrator/async-runner.ts +324 -0
- package/extensions/orchestrator/async-status-parse.ts +31 -0
- package/extensions/orchestrator/async-status-ui.ts +201 -0
- package/extensions/orchestrator/async-wait.ts +36 -0
- package/extensions/orchestrator/btw.ts +210 -0
- package/extensions/orchestrator/cron-status-format.ts +126 -0
- package/extensions/orchestrator/cron-status-ui.ts +132 -0
- package/extensions/orchestrator/cron.ts +730 -0
- package/extensions/orchestrator/dreaming.ts +355 -0
- package/extensions/orchestrator/enforcement-helpers.ts +530 -0
- package/extensions/orchestrator/enforcement-rules.ts +329 -0
- package/extensions/orchestrator/enforcement.ts +828 -0
- package/extensions/orchestrator/extended-autocomplete.ts +593 -0
- package/extensions/orchestrator/git-helpers.ts +420 -0
- package/extensions/orchestrator/github-autocomplete.ts +250 -0
- package/extensions/orchestrator/icons.ts +17 -0
- package/extensions/orchestrator/index.ts +100 -0
- package/extensions/orchestrator/memory-embeddings.ts +281 -0
- package/extensions/orchestrator/memory-promotion.ts +449 -0
- package/extensions/orchestrator/memory-provenance.ts +80 -0
- package/extensions/orchestrator/memory-query-class.ts +90 -0
- package/extensions/orchestrator/memory-scoring.ts +434 -0
- package/extensions/orchestrator/memory-tools.ts +883 -0
- package/extensions/orchestrator/memory-tree.ts +212 -0
- package/extensions/orchestrator/nvim.ts +169 -0
- package/extensions/orchestrator/overlay-dashboard-utils.ts +25 -0
- package/extensions/orchestrator/overlay-dashboard.ts +512 -0
- package/extensions/orchestrator/pi-config-review-state.ts +303 -0
- package/extensions/orchestrator/preference-extractor.ts +106 -0
- package/extensions/orchestrator/project-settings.ts +468 -0
- package/extensions/orchestrator/promotion-queue.ts +311 -0
- package/extensions/orchestrator/resolve-agent-model.ts +32 -0
- package/extensions/orchestrator/review-ui.ts +204 -0
- package/extensions/orchestrator/rule-placeholders.ts +12 -0
- package/extensions/orchestrator/rules.ts +481 -0
- package/extensions/orchestrator/session-search.ts +243 -0
- package/extensions/orchestrator/session-validation.ts +374 -0
- package/extensions/orchestrator/situation-report.ts +370 -0
- package/extensions/orchestrator/status-bar.ts +31 -0
- package/extensions/orchestrator/status-line.ts +192 -0
- package/extensions/orchestrator/status.ts +110 -0
- package/extensions/orchestrator/subagent-tool.ts +1748 -0
- package/extensions/orchestrator/task-lifecycle.ts +88 -0
- package/extensions/orchestrator/utils.ts +227 -0
- package/extensions/pidash/index.ts +15 -0
- package/extensions/pidash/pidash.ts +941 -0
- package/extensions/pidiff/index.ts +13 -0
- package/extensions/pidiff/pidiff.ts +369 -0
- package/extensions/providers/acpx-driver.ts +479 -0
- package/extensions/providers/built-in-drivers.ts +52 -0
- package/extensions/providers/claude-driver.ts +319 -0
- package/extensions/providers/cursor-acpx-driver.ts +398 -0
- package/extensions/providers/cursor-cli-driver.ts +484 -0
- package/extensions/providers/gemini-driver.ts +302 -0
- package/extensions/providers/index.ts +513 -0
- package/extensions/shared/build-system-prompt.ts +57 -0
- package/extensions/shared/create-runtime-provider.ts +190 -0
- package/extensions/shared/daemon-manager.ts +248 -0
- package/extensions/shared/file-logger.ts +115 -0
- package/extensions/shared/managed-refresh.ts +211 -0
- package/extensions/shared/provider-driver.ts +295 -0
- package/extensions/shared/provider-errors.ts +305 -0
- package/extensions/shared/provider-registry.ts +388 -0
- package/extensions/shared/stream-builder.ts +275 -0
- package/extensions/shared/types.ts +20 -0
- package/extensions/shared/ui/README.md +10 -0
- package/extensions/shared/ui/badge.tsx +52 -0
- package/extensions/shared/ui/button.tsx +58 -0
- package/extensions/shared/ui/collapsible.tsx +21 -0
- package/extensions/shared/ui/dropdown-menu.tsx +266 -0
- package/extensions/shared/ui/input.tsx +20 -0
- package/extensions/shared/ui/popover.tsx +114 -0
- package/extensions/shared/ui/resizable.tsx +48 -0
- package/extensions/shared/ui/scroll-area.tsx +53 -0
- package/extensions/shared/ui/select.tsx +201 -0
- package/extensions/shared/ui/separator.tsx +25 -0
- package/extensions/shared/ui/tooltip.tsx +64 -0
- package/extensions/shared/ws-client.ts +74 -0
- package/package.json +59 -0
- package/prompts/coderabbit-rate-limit.md +95 -0
- package/prompts/create-coms-feature-manager.md +186 -0
- package/prompts/create-skill.md +128 -0
- package/prompts/domain-model.md +109 -0
- package/prompts/external-ai.md +665 -0
- package/prompts/implement-and-review.md +25 -0
- package/prompts/implement.md +26 -0
- package/prompts/issue-review.md +256 -0
- package/prompts/pr-review.md +753 -0
- package/prompts/query-db.md +154 -0
- package/prompts/refine-review.md +208 -0
- package/prompts/release.md +235 -0
- package/prompts/remember.md +33 -0
- package/prompts/review-handler-status.md +27 -0
- package/prompts/review-handler.md +677 -0
- package/prompts/review-local.md +159 -0
- package/prompts/scout-and-plan.md +21 -0
- package/rules/00-orchestrator-core.md +42 -0
- package/rules/05-issue-first-workflow.md +119 -0
- package/rules/10-agent-routing.md +58 -0
- package/rules/15-mcp-launchpad.md +46 -0
- package/rules/20-code-review-loop.md +234 -0
- package/rules/25-documentation-updates.md +15 -0
- package/rules/30-prompt-templates.md +19 -0
- package/rules/35-memory.md +240 -0
- package/rules/40-critical-rules.md +124 -0
- package/rules/45-file-preview.md +29 -0
- package/rules/50-agent-bug-reporting.md +90 -0
- package/rules/55-coms-protocol.md +63 -0
- package/rules/60-task-tracking.md +66 -0
- package/skills/conflict-resolver/SKILL.md +213 -0
- package/skills/skill-creator/SKILL.md +85 -0
- package/skills/systematic-debugging/SKILL.md +353 -0
- package/skills/test-driven-development/SKILL.md +335 -0
- package/skills/update-agents-md/SKILL.md +282 -0
- package/templates/coms-feature-manager-prompt.md +313 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tagged error hierarchy for the provider driver system.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from t3code's `Errors.ts`. Uses plain TypeScript classes with
|
|
5
|
+
* a `tag` discriminant instead of Effect's `TaggedErrorClass`. Each error
|
|
6
|
+
* has structured fields (`driver`, `instanceId`, `detail`, `cause`) and
|
|
7
|
+
* a descriptive `message` getter.
|
|
8
|
+
*
|
|
9
|
+
* The `tag` field enables exhaustive switch/case error handling without
|
|
10
|
+
* `instanceof` checks — matching t3code's pattern where Effect's type
|
|
11
|
+
* system ensures all error paths are handled.
|
|
12
|
+
*
|
|
13
|
+
* @module shared/provider-errors
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Base class
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/** Base for all provider errors — carries a `tag` discriminant. */
|
|
21
|
+
export abstract class ProviderError extends Error {
|
|
22
|
+
abstract readonly tag: string;
|
|
23
|
+
readonly cause?: unknown;
|
|
24
|
+
|
|
25
|
+
constructor(message: string, cause?: unknown) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = this.constructor.name;
|
|
28
|
+
this.cause = cause;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Driver-level errors
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* ProviderDriverError — a driver's `create()` call failed before producing
|
|
38
|
+
* an instance. Surfaced to the registry, which marks the offending entry
|
|
39
|
+
* as an "unavailable" shadow snapshot rather than crashing.
|
|
40
|
+
*/
|
|
41
|
+
export class ProviderDriverError extends ProviderError {
|
|
42
|
+
readonly tag = "ProviderDriverError" as const;
|
|
43
|
+
readonly driver: string;
|
|
44
|
+
readonly instanceId: string;
|
|
45
|
+
readonly detail: string;
|
|
46
|
+
|
|
47
|
+
constructor(input: {
|
|
48
|
+
driver: string;
|
|
49
|
+
instanceId: string;
|
|
50
|
+
detail: string;
|
|
51
|
+
cause?: unknown;
|
|
52
|
+
}) {
|
|
53
|
+
super(
|
|
54
|
+
`Provider driver '${input.driver}' failed to create instance '${input.instanceId}': ${input.detail}`,
|
|
55
|
+
input.cause,
|
|
56
|
+
);
|
|
57
|
+
this.driver = input.driver;
|
|
58
|
+
this.instanceId = input.instanceId;
|
|
59
|
+
this.detail = input.detail;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* ProviderProbeError — binary/runtime not available or probe failed.
|
|
65
|
+
*/
|
|
66
|
+
export class ProviderProbeError extends ProviderError {
|
|
67
|
+
readonly tag = "ProviderProbeError" as const;
|
|
68
|
+
readonly driver: string;
|
|
69
|
+
readonly detail: string;
|
|
70
|
+
|
|
71
|
+
constructor(input: {
|
|
72
|
+
driver: string;
|
|
73
|
+
detail: string;
|
|
74
|
+
cause?: unknown;
|
|
75
|
+
}) {
|
|
76
|
+
super(
|
|
77
|
+
`Provider probe failed for '${input.driver}': ${input.detail}`,
|
|
78
|
+
input.cause,
|
|
79
|
+
);
|
|
80
|
+
this.driver = input.driver;
|
|
81
|
+
this.detail = input.detail;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* ProviderValidationError — config validation failed.
|
|
87
|
+
*/
|
|
88
|
+
export class ProviderValidationError extends ProviderError {
|
|
89
|
+
readonly tag = "ProviderValidationError" as const;
|
|
90
|
+
readonly operation: string;
|
|
91
|
+
readonly issue: string;
|
|
92
|
+
|
|
93
|
+
constructor(input: {
|
|
94
|
+
operation: string;
|
|
95
|
+
issue: string;
|
|
96
|
+
cause?: unknown;
|
|
97
|
+
}) {
|
|
98
|
+
super(
|
|
99
|
+
`Provider validation failed in ${input.operation}: ${input.issue}`,
|
|
100
|
+
input.cause,
|
|
101
|
+
);
|
|
102
|
+
this.operation = input.operation;
|
|
103
|
+
this.issue = input.issue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* ProviderInstanceNotFoundError — lookup against the instance registry
|
|
109
|
+
* failed. The driver is registered, but no instance with the requested
|
|
110
|
+
* id has been bootstrapped.
|
|
111
|
+
*/
|
|
112
|
+
export class ProviderInstanceNotFoundError extends ProviderError {
|
|
113
|
+
readonly tag = "ProviderInstanceNotFoundError" as const;
|
|
114
|
+
readonly instanceId: string;
|
|
115
|
+
|
|
116
|
+
constructor(input: {
|
|
117
|
+
instanceId: string;
|
|
118
|
+
cause?: unknown;
|
|
119
|
+
}) {
|
|
120
|
+
super(
|
|
121
|
+
`No provider instance bound to id '${input.instanceId}'`,
|
|
122
|
+
input.cause,
|
|
123
|
+
);
|
|
124
|
+
this.instanceId = input.instanceId;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Adapter-level errors (session / turn runtime)
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* ProviderAdapterValidationError — invalid adapter API input.
|
|
134
|
+
*/
|
|
135
|
+
export class ProviderAdapterValidationError extends ProviderError {
|
|
136
|
+
readonly tag = "ProviderAdapterValidationError" as const;
|
|
137
|
+
readonly provider: string;
|
|
138
|
+
readonly operation: string;
|
|
139
|
+
readonly issue: string;
|
|
140
|
+
|
|
141
|
+
constructor(input: {
|
|
142
|
+
provider: string;
|
|
143
|
+
operation: string;
|
|
144
|
+
issue: string;
|
|
145
|
+
cause?: unknown;
|
|
146
|
+
}) {
|
|
147
|
+
super(
|
|
148
|
+
`Provider adapter validation failed (${input.provider}) in ${input.operation}: ${input.issue}`,
|
|
149
|
+
input.cause,
|
|
150
|
+
);
|
|
151
|
+
this.provider = input.provider;
|
|
152
|
+
this.operation = input.operation;
|
|
153
|
+
this.issue = input.issue;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* ProviderAdapterSessionNotFoundError — adapter-owned session id is unknown.
|
|
159
|
+
*/
|
|
160
|
+
export class ProviderAdapterSessionNotFoundError extends ProviderError {
|
|
161
|
+
readonly tag = "ProviderAdapterSessionNotFoundError" as const;
|
|
162
|
+
readonly provider: string;
|
|
163
|
+
readonly sessionId: string;
|
|
164
|
+
|
|
165
|
+
constructor(input: {
|
|
166
|
+
provider: string;
|
|
167
|
+
sessionId: string;
|
|
168
|
+
cause?: unknown;
|
|
169
|
+
}) {
|
|
170
|
+
super(
|
|
171
|
+
`Unknown ${input.provider} adapter session: ${input.sessionId}`,
|
|
172
|
+
input.cause,
|
|
173
|
+
);
|
|
174
|
+
this.provider = input.provider;
|
|
175
|
+
this.sessionId = input.sessionId;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* ProviderAdapterSessionClosedError — adapter session exists but is closed.
|
|
181
|
+
*/
|
|
182
|
+
export class ProviderAdapterSessionClosedError extends ProviderError {
|
|
183
|
+
readonly tag = "ProviderAdapterSessionClosedError" as const;
|
|
184
|
+
readonly provider: string;
|
|
185
|
+
readonly sessionId: string;
|
|
186
|
+
|
|
187
|
+
constructor(input: {
|
|
188
|
+
provider: string;
|
|
189
|
+
sessionId: string;
|
|
190
|
+
cause?: unknown;
|
|
191
|
+
}) {
|
|
192
|
+
super(
|
|
193
|
+
`${input.provider} adapter session is closed: ${input.sessionId}`,
|
|
194
|
+
input.cause,
|
|
195
|
+
);
|
|
196
|
+
this.provider = input.provider;
|
|
197
|
+
this.sessionId = input.sessionId;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* ProviderAdapterRequestError — provider protocol request failed or timed out.
|
|
203
|
+
*/
|
|
204
|
+
export class ProviderAdapterRequestError extends ProviderError {
|
|
205
|
+
readonly tag = "ProviderAdapterRequestError" as const;
|
|
206
|
+
readonly provider: string;
|
|
207
|
+
readonly method: string;
|
|
208
|
+
readonly detail: string;
|
|
209
|
+
|
|
210
|
+
constructor(input: {
|
|
211
|
+
provider: string;
|
|
212
|
+
method: string;
|
|
213
|
+
detail: string;
|
|
214
|
+
cause?: unknown;
|
|
215
|
+
}) {
|
|
216
|
+
super(
|
|
217
|
+
`Provider adapter request failed (${input.provider}) for ${input.method}: ${input.detail}`,
|
|
218
|
+
input.cause,
|
|
219
|
+
);
|
|
220
|
+
this.provider = input.provider;
|
|
221
|
+
this.method = input.method;
|
|
222
|
+
this.detail = input.detail;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* ProviderAdapterProcessError — provider process lifecycle failure.
|
|
228
|
+
*/
|
|
229
|
+
export class ProviderAdapterProcessError extends ProviderError {
|
|
230
|
+
readonly tag = "ProviderAdapterProcessError" as const;
|
|
231
|
+
readonly provider: string;
|
|
232
|
+
readonly sessionId: string;
|
|
233
|
+
readonly detail: string;
|
|
234
|
+
|
|
235
|
+
constructor(input: {
|
|
236
|
+
provider: string;
|
|
237
|
+
sessionId: string;
|
|
238
|
+
detail: string;
|
|
239
|
+
cause?: unknown;
|
|
240
|
+
}) {
|
|
241
|
+
super(
|
|
242
|
+
`Provider adapter process error (${input.provider}) for session ${input.sessionId}: ${input.detail}`,
|
|
243
|
+
input.cause,
|
|
244
|
+
);
|
|
245
|
+
this.provider = input.provider;
|
|
246
|
+
this.sessionId = input.sessionId;
|
|
247
|
+
this.detail = input.detail;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
// Union types (for exhaustive handling)
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
/** All adapter-level errors. */
|
|
256
|
+
export type ProviderAdapterError =
|
|
257
|
+
| ProviderAdapterValidationError
|
|
258
|
+
| ProviderAdapterSessionNotFoundError
|
|
259
|
+
| ProviderAdapterSessionClosedError
|
|
260
|
+
| ProviderAdapterRequestError
|
|
261
|
+
| ProviderAdapterProcessError;
|
|
262
|
+
|
|
263
|
+
/** All provider-level errors (driver + adapter). */
|
|
264
|
+
export type ProviderServiceError =
|
|
265
|
+
| ProviderDriverError
|
|
266
|
+
| ProviderProbeError
|
|
267
|
+
| ProviderValidationError
|
|
268
|
+
| ProviderInstanceNotFoundError
|
|
269
|
+
| ProviderAdapterError;
|
|
270
|
+
|
|
271
|
+
// ---------------------------------------------------------------------------
|
|
272
|
+
// Unavailable Snapshot
|
|
273
|
+
// ---------------------------------------------------------------------------
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Build an "unavailable" shadow snapshot for a configured instance whose
|
|
277
|
+
* driver failed to probe, create, or validate. The result is structured
|
|
278
|
+
* so consumers can render a "missing/failed provider" affordance.
|
|
279
|
+
*
|
|
280
|
+
* Adapted from t3code's `buildUnavailableProviderSnapshot`.
|
|
281
|
+
*/
|
|
282
|
+
export interface UnavailableProviderSnapshot {
|
|
283
|
+
readonly instanceId: string;
|
|
284
|
+
readonly driverKind: string;
|
|
285
|
+
readonly displayName: string;
|
|
286
|
+
readonly available: false;
|
|
287
|
+
readonly reason: string;
|
|
288
|
+
readonly checkedAt: string;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export function buildUnavailableSnapshot(input: {
|
|
292
|
+
instanceId: string;
|
|
293
|
+
driverKind: string;
|
|
294
|
+
displayName?: string;
|
|
295
|
+
reason: string;
|
|
296
|
+
}): UnavailableProviderSnapshot {
|
|
297
|
+
return {
|
|
298
|
+
instanceId: input.instanceId,
|
|
299
|
+
driverKind: input.driverKind,
|
|
300
|
+
displayName: input.displayName?.trim() || input.driverKind,
|
|
301
|
+
available: false,
|
|
302
|
+
reason: input.reason,
|
|
303
|
+
checkedAt: new Date().toISOString(),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProviderDriverRegistry — runtime registry for driver instances.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from t3code's `ProviderInstanceRegistryLive.ts`. Uses plain
|
|
5
|
+
* TypeScript (no Effect dependency) — Maps and async functions replace
|
|
6
|
+
* Effect Refs and Scopes.
|
|
7
|
+
*
|
|
8
|
+
* Materializes every entry from a config map:
|
|
9
|
+
* - Known driver + valid config → `probe()` → `create()` → live instance
|
|
10
|
+
* - Known driver + failed probe → unavailable shadow snapshot
|
|
11
|
+
* - Known driver + failed config decode → unavailable shadow snapshot
|
|
12
|
+
* - Known driver + failed create → unavailable shadow snapshot
|
|
13
|
+
* - Unknown driver → unavailable shadow snapshot
|
|
14
|
+
*
|
|
15
|
+
* Every live instance owns its own state (no shared mutables). Tearing
|
|
16
|
+
* down one instance cannot affect another. `teardownAll()` disposes
|
|
17
|
+
* every instance in reverse-registration order.
|
|
18
|
+
*
|
|
19
|
+
* @module shared/provider-registry
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type {
|
|
23
|
+
AnyProviderDriver,
|
|
24
|
+
ProviderInstance,
|
|
25
|
+
ProviderSnapshot,
|
|
26
|
+
} from "./provider-driver.js";
|
|
27
|
+
import {
|
|
28
|
+
ProviderDriverError,
|
|
29
|
+
ProviderInstanceNotFoundError,
|
|
30
|
+
ProviderValidationError,
|
|
31
|
+
buildUnavailableSnapshot,
|
|
32
|
+
type UnavailableProviderSnapshot,
|
|
33
|
+
} from "./provider-errors.js";
|
|
34
|
+
import { fileLog } from "./file-logger.js";
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Types
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
|
|
40
|
+
/** Live registry entry: the materialized instance + the original config. */
|
|
41
|
+
interface LiveEntry {
|
|
42
|
+
readonly instance: ProviderInstance;
|
|
43
|
+
readonly rawConfig: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Config for one instance (from settings). */
|
|
47
|
+
export interface ProviderInstanceConfig {
|
|
48
|
+
readonly driver: string;
|
|
49
|
+
readonly displayName?: string;
|
|
50
|
+
readonly enabled?: boolean;
|
|
51
|
+
readonly config?: unknown;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Config map keyed by instance id. */
|
|
55
|
+
export type ProviderInstanceConfigMap = Record<string, ProviderInstanceConfig>;
|
|
56
|
+
|
|
57
|
+
/** Change listener callback. */
|
|
58
|
+
export type RegistryChangeListener = () => void;
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Registry
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
const LOG_DOMAIN = "provider-registry";
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* ProviderDriverRegistry — owns the lifecycle of all provider instances.
|
|
68
|
+
*
|
|
69
|
+
* ```ts
|
|
70
|
+
* const registry = new ProviderDriverRegistry();
|
|
71
|
+
* registry.registerDriver(claudeDriver);
|
|
72
|
+
* registry.registerDriver(cursorCliDriver);
|
|
73
|
+
* await registry.reconcile(configMap, cwd);
|
|
74
|
+
* const instance = registry.getInstance("cli-cursor");
|
|
75
|
+
* await registry.teardownAll();
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export class ProviderDriverRegistry {
|
|
79
|
+
private readonly drivers = new Map<string, AnyProviderDriver>();
|
|
80
|
+
private readonly entries = new Map<string, LiveEntry>();
|
|
81
|
+
private readonly unavailable = new Map<string, UnavailableProviderSnapshot>();
|
|
82
|
+
private readonly changeListeners = new Set<RegistryChangeListener>();
|
|
83
|
+
|
|
84
|
+
// -------------------------------------------------------------------------
|
|
85
|
+
// Driver registration
|
|
86
|
+
// -------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
/** Register a driver. Replaces any existing driver with the same `driverKind`. */
|
|
89
|
+
registerDriver(driver: AnyProviderDriver): void {
|
|
90
|
+
this.drivers.set(driver.driverKind, driver);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Get a registered driver by kind. */
|
|
94
|
+
getDriver(driverKind: string): AnyProviderDriver | undefined {
|
|
95
|
+
return this.drivers.get(driverKind);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** List all registered driver kinds. */
|
|
99
|
+
listDriverKinds(): string[] {
|
|
100
|
+
return [...this.drivers.keys()];
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
104
|
+
// Instance access
|
|
105
|
+
// -------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
/** Look up one instance by id. Returns undefined when not found. */
|
|
108
|
+
getInstance(instanceId: string): ProviderInstance | undefined {
|
|
109
|
+
return this.entries.get(instanceId)?.instance;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Every available (successfully created) instance, in registration order. */
|
|
113
|
+
listInstances(): ProviderInstance[] {
|
|
114
|
+
return [...this.entries.values()].map((e) => e.instance);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Shadow snapshots for instances whose driver is unknown or failed. */
|
|
118
|
+
listUnavailable(): UnavailableProviderSnapshot[] {
|
|
119
|
+
return [...this.unavailable.values()];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get snapshot for an instance — real if live, shadow if unavailable.
|
|
124
|
+
* Returns undefined if the instance id is completely unknown.
|
|
125
|
+
*/
|
|
126
|
+
getSnapshot(instanceId: string): ProviderSnapshot | UnavailableProviderSnapshot | undefined {
|
|
127
|
+
const live = this.entries.get(instanceId);
|
|
128
|
+
if (live) return live.instance.snapshot.getSnapshot();
|
|
129
|
+
return this.unavailable.get(instanceId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** True if instance exists (live or unavailable). */
|
|
133
|
+
hasInstance(instanceId: string): boolean {
|
|
134
|
+
return this.entries.has(instanceId) || this.unavailable.has(instanceId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// -------------------------------------------------------------------------
|
|
138
|
+
// Change notification
|
|
139
|
+
// -------------------------------------------------------------------------
|
|
140
|
+
|
|
141
|
+
/** Subscribe to registry changes. Returns unsubscribe function. */
|
|
142
|
+
onChange(listener: RegistryChangeListener): () => void {
|
|
143
|
+
this.changeListeners.add(listener);
|
|
144
|
+
return () => this.changeListeners.delete(listener);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private notifyChange(): void {
|
|
148
|
+
for (const listener of this.changeListeners) {
|
|
149
|
+
try {
|
|
150
|
+
listener();
|
|
151
|
+
} catch {
|
|
152
|
+
/* ignore listener errors */
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// -------------------------------------------------------------------------
|
|
158
|
+
// Reconciliation
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reconcile the registry against a config map. Tears down removed/changed
|
|
163
|
+
* instances, builds new ones. Unchanged instances are kept as-is.
|
|
164
|
+
*
|
|
165
|
+
* Adapted from t3code's `makeReconcile`.
|
|
166
|
+
*/
|
|
167
|
+
async reconcile(configMap: ProviderInstanceConfigMap, cwd: string): Promise<void> {
|
|
168
|
+
const nextKeys = new Set(Object.keys(configMap));
|
|
169
|
+
|
|
170
|
+
// 1. Identify removed and changed instances
|
|
171
|
+
const toRemove: string[] = [];
|
|
172
|
+
const toReplace = new Set<string>();
|
|
173
|
+
|
|
174
|
+
for (const [instanceId, entry] of this.entries) {
|
|
175
|
+
if (!nextKeys.has(instanceId)) {
|
|
176
|
+
toRemove.push(instanceId);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
const nextConfig = configMap[instanceId];
|
|
180
|
+
if (nextConfig && !this.configEqual(entry.rawConfig, nextConfig)) {
|
|
181
|
+
toReplace.add(instanceId);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Also remove unavailable entries that disappeared from config
|
|
186
|
+
let changed = toRemove.length > 0;
|
|
187
|
+
for (const instanceId of this.unavailable.keys()) {
|
|
188
|
+
if (!nextKeys.has(instanceId)) {
|
|
189
|
+
this.unavailable.delete(instanceId);
|
|
190
|
+
changed = true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 2. Tear down removed/replaced instances
|
|
195
|
+
for (const id of [...toRemove, ...toReplace]) {
|
|
196
|
+
await this.teardownInstance(id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 3. Build new and replaced instances
|
|
200
|
+
|
|
201
|
+
for (const [instanceId, config] of Object.entries(configMap)) {
|
|
202
|
+
// Skip unchanged live entries
|
|
203
|
+
if (this.entries.has(instanceId) && !toReplace.has(instanceId)) {
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const result = await this.buildEntry(instanceId, config, cwd);
|
|
208
|
+
if (result.kind === "live") {
|
|
209
|
+
this.entries.set(instanceId, result.live);
|
|
210
|
+
this.unavailable.delete(instanceId);
|
|
211
|
+
} else {
|
|
212
|
+
this.unavailable.set(instanceId, result.snapshot);
|
|
213
|
+
this.entries.delete(instanceId);
|
|
214
|
+
}
|
|
215
|
+
changed = true;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (changed) {
|
|
219
|
+
this.notifyChange();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Create a single instance from a config entry. Does not add to the
|
|
225
|
+
* registry — use `reconcile()` for that. Exported for direct use when
|
|
226
|
+
* building instances one at a time during extension load.
|
|
227
|
+
*/
|
|
228
|
+
async createInstance(
|
|
229
|
+
instanceId: string,
|
|
230
|
+
config: ProviderInstanceConfig,
|
|
231
|
+
cwd: string,
|
|
232
|
+
): Promise<ProviderInstance> {
|
|
233
|
+
const result = await this.buildEntry(instanceId, config, cwd);
|
|
234
|
+
if (result.kind === "unavailable") {
|
|
235
|
+
throw new ProviderDriverError({
|
|
236
|
+
driver: config.driver,
|
|
237
|
+
instanceId,
|
|
238
|
+
detail: result.snapshot.reason,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
this.entries.set(instanceId, result.live);
|
|
242
|
+
this.notifyChange();
|
|
243
|
+
return result.live.instance;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// -------------------------------------------------------------------------
|
|
247
|
+
// Teardown
|
|
248
|
+
// -------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
/** Tear down one instance by id. No-op if not found. */
|
|
251
|
+
async teardownInstance(id: string): Promise<void> {
|
|
252
|
+
const entry = this.entries.get(id);
|
|
253
|
+
if (!entry) return;
|
|
254
|
+
this.entries.delete(id);
|
|
255
|
+
try {
|
|
256
|
+
await entry.instance.dispose();
|
|
257
|
+
} catch (err) {
|
|
258
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN, `dispose failed for ${id}:`, err);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** Tear down all instances (reverse order). Clears unavailable entries. */
|
|
263
|
+
async teardownAll(): Promise<void> {
|
|
264
|
+
const ids = [...this.entries.keys()].reverse();
|
|
265
|
+
for (const id of ids) {
|
|
266
|
+
await this.teardownInstance(id);
|
|
267
|
+
}
|
|
268
|
+
this.unavailable.clear();
|
|
269
|
+
this.notifyChange();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// -------------------------------------------------------------------------
|
|
273
|
+
// Private
|
|
274
|
+
// -------------------------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
private configEqual(
|
|
277
|
+
prev: unknown,
|
|
278
|
+
next: ProviderInstanceConfig,
|
|
279
|
+
): boolean {
|
|
280
|
+
try {
|
|
281
|
+
return JSON.stringify(prev) === JSON.stringify(next);
|
|
282
|
+
} catch {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private async buildEntry(
|
|
288
|
+
instanceId: string,
|
|
289
|
+
config: ProviderInstanceConfig,
|
|
290
|
+
cwd: string,
|
|
291
|
+
): Promise<
|
|
292
|
+
| { readonly kind: "live"; readonly live: LiveEntry }
|
|
293
|
+
| { readonly kind: "unavailable"; readonly snapshot: UnavailableProviderSnapshot }
|
|
294
|
+
> {
|
|
295
|
+
const driver = this.drivers.get(config.driver);
|
|
296
|
+
|
|
297
|
+
// Unknown driver → unavailable
|
|
298
|
+
if (!driver) {
|
|
299
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
300
|
+
`${instanceId}: driver '${config.driver}' not registered`);
|
|
301
|
+
return {
|
|
302
|
+
kind: "unavailable",
|
|
303
|
+
snapshot: buildUnavailableSnapshot({
|
|
304
|
+
instanceId,
|
|
305
|
+
driverKind: config.driver,
|
|
306
|
+
displayName: config.displayName,
|
|
307
|
+
reason: `Driver '${config.driver}' is not registered in this build.`,
|
|
308
|
+
}),
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Validate config
|
|
313
|
+
let typedConfig: unknown;
|
|
314
|
+
try {
|
|
315
|
+
const rawConfig = config.config ?? driver.defaultConfig();
|
|
316
|
+
typedConfig = driver.configSchema.parse(rawConfig);
|
|
317
|
+
} catch (err) {
|
|
318
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
319
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
320
|
+
`${instanceId}: config validation failed: ${detail}`);
|
|
321
|
+
return {
|
|
322
|
+
kind: "unavailable",
|
|
323
|
+
snapshot: buildUnavailableSnapshot({
|
|
324
|
+
instanceId,
|
|
325
|
+
driverKind: config.driver,
|
|
326
|
+
displayName: config.displayName,
|
|
327
|
+
reason: `Invalid config for '${instanceId}': ${detail}`,
|
|
328
|
+
}),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Probe
|
|
333
|
+
try {
|
|
334
|
+
const probeResult = await driver.probe(typedConfig);
|
|
335
|
+
if (!probeResult.available) {
|
|
336
|
+
fileLog(LOG_DOMAIN, "info", LOG_DOMAIN,
|
|
337
|
+
`${instanceId}: probe unavailable — ${probeResult.reason || "unknown reason"}`);
|
|
338
|
+
return {
|
|
339
|
+
kind: "unavailable",
|
|
340
|
+
snapshot: buildUnavailableSnapshot({
|
|
341
|
+
instanceId,
|
|
342
|
+
driverKind: config.driver,
|
|
343
|
+
displayName: config.displayName,
|
|
344
|
+
reason: probeResult.reason || `${config.driver} is not available`,
|
|
345
|
+
}),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
} catch (err) {
|
|
349
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
350
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
351
|
+
`${instanceId}: probe threw: ${detail}`);
|
|
352
|
+
return {
|
|
353
|
+
kind: "unavailable",
|
|
354
|
+
snapshot: buildUnavailableSnapshot({
|
|
355
|
+
instanceId,
|
|
356
|
+
driverKind: config.driver,
|
|
357
|
+
displayName: config.displayName,
|
|
358
|
+
reason: `Probe failed: ${detail}`,
|
|
359
|
+
}),
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Create instance
|
|
364
|
+
try {
|
|
365
|
+
const instance = await driver.create({
|
|
366
|
+
instanceId,
|
|
367
|
+
displayName: config.displayName,
|
|
368
|
+
enabled: config.enabled ?? true,
|
|
369
|
+
config: typedConfig,
|
|
370
|
+
cwd,
|
|
371
|
+
});
|
|
372
|
+
return { kind: "live", live: { instance, rawConfig: config } };
|
|
373
|
+
} catch (err) {
|
|
374
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
375
|
+
fileLog(LOG_DOMAIN, "warn", LOG_DOMAIN,
|
|
376
|
+
`${instanceId}: create failed: ${detail}`);
|
|
377
|
+
return {
|
|
378
|
+
kind: "unavailable",
|
|
379
|
+
snapshot: buildUnavailableSnapshot({
|
|
380
|
+
instanceId,
|
|
381
|
+
driverKind: config.driver,
|
|
382
|
+
displayName: config.displayName,
|
|
383
|
+
reason: `Driver '${config.driver}' failed to create instance: ${detail}`,
|
|
384
|
+
}),
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|