priiisk 0.1.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.
- package/README.md +41 -0
- package/dist/chunk-F77DHFYU.js +988 -0
- package/dist/chunk-FZICDHTN.js +603 -0
- package/dist/chunk-KFSFN6L5.js +44744 -0
- package/dist/chunk-PIZUXHCN.js +159 -0
- package/dist/chunk-RJO6Z3SQ.js +14714 -0
- package/dist/chunk-RJO6Z3SQ.js.LEGAL.txt +38 -0
- package/dist/chunk-UCN75R46.js +1185 -0
- package/dist/chunk-YCBBC5MH.js +26660 -0
- package/dist/chunk-YCBBC5MH.js.LEGAL.txt +7 -0
- package/dist/chunk-Z64QXV3O.js +2547 -0
- package/dist/priiisk-host.js +27171 -0
- package/dist/priiisk.js +20661 -0
- package/package.json +33 -0
- package/profiles/_base/instructions.md +37 -0
- package/profiles/assayer/instructions.md +13 -0
- package/profiles/prospector/instructions.md +13 -0
- package/profiles/wright/instructions.md +18 -0
- package/worker-skills/codegraph/SKILL.md +21 -0
|
@@ -0,0 +1,2547 @@
|
|
|
1
|
+
import { createRequire as __priiiskCreateRequire } from "node:module";
|
|
2
|
+
const require = __priiiskCreateRequire(import.meta.url);
|
|
3
|
+
import {
|
|
4
|
+
CampAskState,
|
|
5
|
+
Cause_exports,
|
|
6
|
+
Clock_exports,
|
|
7
|
+
Context_exports,
|
|
8
|
+
Data_exports,
|
|
9
|
+
Deferred_exports,
|
|
10
|
+
Effect_exports,
|
|
11
|
+
FiberSet_exports,
|
|
12
|
+
Match_exports,
|
|
13
|
+
Stream_exports,
|
|
14
|
+
SubscriptionRef_exports
|
|
15
|
+
} from "./chunk-KFSFN6L5.js";
|
|
16
|
+
|
|
17
|
+
// packages/host-ui/src/backend/campUiBackend.ts
|
|
18
|
+
var CampUiBackendError = class extends Data_exports.TaggedError("CampUiBackendError") {
|
|
19
|
+
};
|
|
20
|
+
var CampUiBackend = class extends Context_exports.Tag("priiisk/CampUiBackend")() {
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// packages/host-ui/src/model/campUiModel.ts
|
|
24
|
+
var CampUiPlacement = {
|
|
25
|
+
external: "external",
|
|
26
|
+
embedded: "embedded"
|
|
27
|
+
};
|
|
28
|
+
var CampUiHealthStatus = {
|
|
29
|
+
ready: "ready",
|
|
30
|
+
degraded: "degraded",
|
|
31
|
+
failed: "failed"
|
|
32
|
+
};
|
|
33
|
+
var CampUiScreen = {
|
|
34
|
+
camp: "camp",
|
|
35
|
+
worker: "worker"
|
|
36
|
+
};
|
|
37
|
+
var campUiAskBlockId = (askId) => `ask:${askId}`;
|
|
38
|
+
var makeInitialCampUiState = (snapshot, presentationNow = 0) => ({
|
|
39
|
+
...snapshot.workers[0] === void 0 ? {} : { selectedWorkerId: snapshot.workers[0].snapshot.worker.id },
|
|
40
|
+
screen: CampUiScreen.camp,
|
|
41
|
+
focusedSlotId: "roster",
|
|
42
|
+
detailVisible: false,
|
|
43
|
+
presentationNow,
|
|
44
|
+
drafts: /* @__PURE__ */ new Map(),
|
|
45
|
+
scrollOffsets: /* @__PURE__ */ new Map(),
|
|
46
|
+
expandedDetailBlockIds: /* @__PURE__ */ new Set()
|
|
47
|
+
});
|
|
48
|
+
var makeCampUiModel = (snapshot, presentationNow = 0) => ({
|
|
49
|
+
snapshot,
|
|
50
|
+
state: makeInitialCampUiState(snapshot, presentationNow)
|
|
51
|
+
});
|
|
52
|
+
var findCampUiWorker = (snapshot, workerId) => snapshot.workers.find((candidate) => candidate.snapshot.worker.id === workerId);
|
|
53
|
+
|
|
54
|
+
// packages/host-ui/src/backend/campUiReconcile.ts
|
|
55
|
+
var retainWorkerEntries = (entries, workerIds) => new Map([...entries].filter(([workerId]) => workerIds.has(workerId)));
|
|
56
|
+
var reconcileCampUiState = (current, snapshot) => {
|
|
57
|
+
if (current.snapshot.generation !== snapshot.generation) {
|
|
58
|
+
return makeInitialCampUiState(snapshot, current.state.presentationNow);
|
|
59
|
+
}
|
|
60
|
+
const workerIds = new Set(snapshot.workers.map((worker) => worker.snapshot.worker.id));
|
|
61
|
+
const previousId = current.state.selectedWorkerId;
|
|
62
|
+
const previousIndex = current.snapshot.workers.findIndex(
|
|
63
|
+
(worker) => worker.snapshot.worker.id === previousId
|
|
64
|
+
);
|
|
65
|
+
const fallbackIndex = Math.min(Math.max(previousIndex, 0), snapshot.workers.length - 1);
|
|
66
|
+
const selectedWorkerId = previousId !== void 0 && workerIds.has(previousId) ? previousId : snapshot.workers[fallbackIndex]?.snapshot.worker.id;
|
|
67
|
+
const {
|
|
68
|
+
selectedWorkerId: _selectedWorkerId,
|
|
69
|
+
focusedDetailBlockId: previousFocusedBlockId,
|
|
70
|
+
...stableState
|
|
71
|
+
} = current.state;
|
|
72
|
+
const askBlockIds = new Set(
|
|
73
|
+
snapshot.workers.flatMap((worker) => worker.asks.map((ask) => campUiAskBlockId(ask.askId)))
|
|
74
|
+
);
|
|
75
|
+
const expandedDetailBlockIds = new Set(
|
|
76
|
+
[...current.state.expandedDetailBlockIds].filter(
|
|
77
|
+
(blockId) => !blockId.startsWith("ask:") || askBlockIds.has(blockId)
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
const focusedDetailBlockId = previousFocusedBlockId !== void 0 && (!previousFocusedBlockId.startsWith("ask:") || askBlockIds.has(previousFocusedBlockId)) ? previousFocusedBlockId : void 0;
|
|
81
|
+
return {
|
|
82
|
+
...stableState,
|
|
83
|
+
...selectedWorkerId === void 0 ? {} : { selectedWorkerId },
|
|
84
|
+
...focusedDetailBlockId === void 0 ? {} : { focusedDetailBlockId },
|
|
85
|
+
screen: selectedWorkerId === void 0 ? CampUiScreen.camp : current.state.screen,
|
|
86
|
+
focusedSlotId: selectedWorkerId === void 0 ? "roster" : current.state.focusedSlotId,
|
|
87
|
+
drafts: retainWorkerEntries(current.state.drafts, workerIds),
|
|
88
|
+
scrollOffsets: retainWorkerEntries(current.state.scrollOffsets, workerIds),
|
|
89
|
+
expandedDetailBlockIds
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// packages/host-ui/src/backend/campUiStoreState.ts
|
|
94
|
+
var updateCampUiWorkerMap = (current, workerId, value) => new Map(current).set(workerId, value);
|
|
95
|
+
var updateCampUiStringSet = (current, value, included) => {
|
|
96
|
+
const next = new Set(current);
|
|
97
|
+
if (included) next.add(value);
|
|
98
|
+
else next.delete(value);
|
|
99
|
+
return next;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// packages/host-ui/src/backend/campUiStore.ts
|
|
103
|
+
var CampUiControllerError = class extends Data_exports.TaggedError("CampUiControllerError") {
|
|
104
|
+
};
|
|
105
|
+
var makeCampUiStore = (initial, presentationNow = 0) => Effect_exports.gen(function* () {
|
|
106
|
+
const modelRef = yield* SubscriptionRef_exports.make(makeCampUiModel(initial, presentationNow));
|
|
107
|
+
const updateState = (mutate) => SubscriptionRef_exports.update(modelRef, (model) => ({ ...model, state: mutate(model.state) }));
|
|
108
|
+
const controller = {
|
|
109
|
+
selectWorker: (workerId) => SubscriptionRef_exports.modifyEffect(
|
|
110
|
+
modelRef,
|
|
111
|
+
(model) => findCampUiWorker(model.snapshot, workerId) === void 0 ? Effect_exports.fail(
|
|
112
|
+
new CampUiControllerError({
|
|
113
|
+
operation: "campUi.selectWorker",
|
|
114
|
+
code: "worker_not_found",
|
|
115
|
+
workerId
|
|
116
|
+
})
|
|
117
|
+
) : Effect_exports.succeed([
|
|
118
|
+
void 0,
|
|
119
|
+
{
|
|
120
|
+
...model,
|
|
121
|
+
state: { ...model.state, selectedWorkerId: workerId }
|
|
122
|
+
}
|
|
123
|
+
])
|
|
124
|
+
),
|
|
125
|
+
openWorker: (workerId) => SubscriptionRef_exports.modifyEffect(
|
|
126
|
+
modelRef,
|
|
127
|
+
(model) => findCampUiWorker(model.snapshot, workerId) === void 0 ? Effect_exports.fail(
|
|
128
|
+
new CampUiControllerError({
|
|
129
|
+
operation: "campUi.openWorker",
|
|
130
|
+
code: "worker_not_found",
|
|
131
|
+
workerId
|
|
132
|
+
})
|
|
133
|
+
) : Effect_exports.succeed([
|
|
134
|
+
void 0,
|
|
135
|
+
{
|
|
136
|
+
...model,
|
|
137
|
+
state: {
|
|
138
|
+
...model.state,
|
|
139
|
+
selectedWorkerId: workerId,
|
|
140
|
+
screen: CampUiScreen.worker
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
])
|
|
144
|
+
),
|
|
145
|
+
activateScreen: (screen) => SubscriptionRef_exports.modifyEffect(
|
|
146
|
+
modelRef,
|
|
147
|
+
(model) => screen === CampUiScreen.worker && model.state.selectedWorkerId === void 0 ? Effect_exports.fail(
|
|
148
|
+
new CampUiControllerError({
|
|
149
|
+
operation: "campUi.activateScreen",
|
|
150
|
+
code: "worker_not_selected"
|
|
151
|
+
})
|
|
152
|
+
) : Effect_exports.succeed([void 0, { ...model, state: { ...model.state, screen } }])
|
|
153
|
+
),
|
|
154
|
+
focusDetailBlock: (focusedDetailBlockId) => updateState((state) => {
|
|
155
|
+
if (focusedDetailBlockId !== void 0) return { ...state, focusedDetailBlockId };
|
|
156
|
+
const { focusedDetailBlockId: _focusedDetailBlockId, ...rest } = state;
|
|
157
|
+
return rest;
|
|
158
|
+
}),
|
|
159
|
+
focusSlot: (focusedSlotId) => updateState((state) => ({ ...state, focusedSlotId })),
|
|
160
|
+
setDraft: (workerId, draft) => updateState((state) => ({
|
|
161
|
+
...state,
|
|
162
|
+
drafts: updateCampUiWorkerMap(state.drafts, workerId, draft)
|
|
163
|
+
})),
|
|
164
|
+
setDetailVisible: (detailVisible) => updateState((state) => {
|
|
165
|
+
if (state.detailVisible === detailVisible) return state;
|
|
166
|
+
return { ...state, detailVisible };
|
|
167
|
+
}),
|
|
168
|
+
setDetailBlockExpanded: (blockId, expanded) => updateState((state) => ({
|
|
169
|
+
...state,
|
|
170
|
+
expandedDetailBlockIds: updateCampUiStringSet(
|
|
171
|
+
state.expandedDetailBlockIds,
|
|
172
|
+
blockId,
|
|
173
|
+
expanded
|
|
174
|
+
)
|
|
175
|
+
})),
|
|
176
|
+
setPresentationNow: (now) => updateState((state) => {
|
|
177
|
+
const presentationNow2 = Math.max(0, Math.floor(now));
|
|
178
|
+
return presentationNow2 === state.presentationNow ? state : { ...state, presentationNow: presentationNow2 };
|
|
179
|
+
}),
|
|
180
|
+
setScrollOffset: (workerId, offset) => updateState((state) => ({
|
|
181
|
+
...state,
|
|
182
|
+
scrollOffsets: updateCampUiWorkerMap(state.scrollOffsets, workerId, Math.max(0, offset))
|
|
183
|
+
})),
|
|
184
|
+
setActionError: (message) => updateState((state) => {
|
|
185
|
+
if (message !== void 0) return { ...state, lastActionError: message };
|
|
186
|
+
const { lastActionError: _lastActionError, ...rest } = state;
|
|
187
|
+
return rest;
|
|
188
|
+
})
|
|
189
|
+
};
|
|
190
|
+
return {
|
|
191
|
+
current: SubscriptionRef_exports.get(modelRef),
|
|
192
|
+
changes: modelRef.changes,
|
|
193
|
+
updateSnapshot: (snapshot) => SubscriptionRef_exports.modify(modelRef, (current) => {
|
|
194
|
+
const stale = current.snapshot.generation === snapshot.generation && snapshot.revision <= current.snapshot.revision;
|
|
195
|
+
return stale ? [false, current] : [true, { snapshot, state: reconcileCampUiState(current, snapshot) }];
|
|
196
|
+
}),
|
|
197
|
+
controller
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// packages/host-ui/src/composition/campUiComponent.ts
|
|
202
|
+
var makeCampUiComponentGroup = (components) => {
|
|
203
|
+
const inputComponents = components.some((component) => component.handleInput !== void 0);
|
|
204
|
+
const pointerComponents = components.some((component) => component.handlePointer !== void 0);
|
|
205
|
+
const invalidatingComponents = components.some((component) => component.invalidate !== void 0);
|
|
206
|
+
return {
|
|
207
|
+
render: (viewport) => components.flatMap((component) => component.render(viewport)),
|
|
208
|
+
...components.some((component) => component.focusable === true) ? { focusable: true } : {},
|
|
209
|
+
...inputComponents ? {
|
|
210
|
+
handleInput: (input) => [...components].reverse().some((component) => component.handleInput?.(input) === true)
|
|
211
|
+
} : {},
|
|
212
|
+
...pointerComponents ? {
|
|
213
|
+
handlePointer: (pointer) => {
|
|
214
|
+
for (const component of [...components].reverse()) {
|
|
215
|
+
const intents = component.handlePointer?.(pointer) ?? [];
|
|
216
|
+
if (intents.length > 0) return intents;
|
|
217
|
+
}
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
} : {},
|
|
221
|
+
...invalidatingComponents ? {
|
|
222
|
+
invalidate: () => {
|
|
223
|
+
for (const component of components) component.invalidate?.();
|
|
224
|
+
}
|
|
225
|
+
} : {}
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
var emptyCampUiComponent = {
|
|
229
|
+
render: () => []
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// packages/host-ui/src/composition/campUiSlots.ts
|
|
233
|
+
var CampUiSlot = {
|
|
234
|
+
campHeader: "camp-header",
|
|
235
|
+
seat: "seat",
|
|
236
|
+
resources: "resources",
|
|
237
|
+
roster: "roster",
|
|
238
|
+
sessionHeader: "session-header",
|
|
239
|
+
transcript: "transcript",
|
|
240
|
+
composer: "composer",
|
|
241
|
+
campFooter: "camp-footer",
|
|
242
|
+
sessionFooter: "session-footer",
|
|
243
|
+
overlay: "overlay"
|
|
244
|
+
};
|
|
245
|
+
var CampUiContributionStrategy = {
|
|
246
|
+
append: "append",
|
|
247
|
+
replace: "replace",
|
|
248
|
+
decorate: "decorate"
|
|
249
|
+
};
|
|
250
|
+
var CampUiCompositionError = class extends Data_exports.TaggedError("CampUiCompositionError") {
|
|
251
|
+
};
|
|
252
|
+
var orderContributions = (contributions) => [...contributions].sort(
|
|
253
|
+
(left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id)
|
|
254
|
+
);
|
|
255
|
+
var registerDispose = (component) => component.dispose === void 0 ? Effect_exports.succeed(component) : Effect_exports.addFinalizer(() => component.dispose).pipe(
|
|
256
|
+
Effect_exports.as(component)
|
|
257
|
+
);
|
|
258
|
+
var validateContributions = (defaults, contributions) => {
|
|
259
|
+
const ids = [...defaults, ...contributions].map((contribution) => contribution.id);
|
|
260
|
+
const duplicateId = ids.find((id, index) => ids.indexOf(id) !== index);
|
|
261
|
+
if (duplicateId !== void 0) {
|
|
262
|
+
return Effect_exports.fail(
|
|
263
|
+
new CampUiCompositionError({ code: "duplicate_id", details: { id: duplicateId } })
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
const replacementSlots = contributions.filter((contribution) => contribution.strategy === CampUiContributionStrategy.replace).map((contribution) => contribution.slotId);
|
|
267
|
+
const duplicateSlot = replacementSlots.find(
|
|
268
|
+
(slotId, index) => replacementSlots.indexOf(slotId) !== index
|
|
269
|
+
);
|
|
270
|
+
return duplicateSlot === void 0 ? Effect_exports.void : Effect_exports.fail(
|
|
271
|
+
new CampUiCompositionError({
|
|
272
|
+
code: "multiple_replacements",
|
|
273
|
+
details: { slotId: duplicateSlot }
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
var composeCampUiSlots = (context, defaults, contributions) => Effect_exports.gen(function* () {
|
|
278
|
+
yield* validateContributions(defaults, contributions);
|
|
279
|
+
const slotIds = [...new Set([...defaults, ...contributions].map((item) => item.slotId))];
|
|
280
|
+
const entries = yield* Effect_exports.forEach(
|
|
281
|
+
slotIds,
|
|
282
|
+
(slotId) => Effect_exports.gen(function* () {
|
|
283
|
+
const replacement = contributions.find(
|
|
284
|
+
(item) => item.slotId === slotId && item.strategy === CampUiContributionStrategy.replace
|
|
285
|
+
);
|
|
286
|
+
const base = replacement === void 0 ? defaults.filter((item) => item.slotId === slotId) : [];
|
|
287
|
+
const appended = contributions.filter(
|
|
288
|
+
(item) => item.slotId === slotId && item.strategy === CampUiContributionStrategy.append
|
|
289
|
+
);
|
|
290
|
+
const contentFactories = orderContributions([
|
|
291
|
+
...base,
|
|
292
|
+
...replacement === void 0 ? [] : [replacement],
|
|
293
|
+
...appended
|
|
294
|
+
]);
|
|
295
|
+
const components = yield* Effect_exports.forEach(
|
|
296
|
+
contentFactories,
|
|
297
|
+
(item) => item.mount(context).pipe(Effect_exports.flatMap(registerDispose))
|
|
298
|
+
);
|
|
299
|
+
let content = components.length === 0 ? emptyCampUiComponent : makeCampUiComponentGroup(components);
|
|
300
|
+
const decorators = orderContributions(
|
|
301
|
+
contributions.filter(
|
|
302
|
+
(item) => item.slotId === slotId && item.strategy === CampUiContributionStrategy.decorate
|
|
303
|
+
)
|
|
304
|
+
);
|
|
305
|
+
for (const decorator of decorators) {
|
|
306
|
+
content = yield* decorator.decorate(context, content).pipe(Effect_exports.flatMap(registerDispose));
|
|
307
|
+
}
|
|
308
|
+
return [slotId, content];
|
|
309
|
+
})
|
|
310
|
+
);
|
|
311
|
+
return new Map(entries);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// packages/host-ui/src/composition/campUiDetailBlocks.ts
|
|
315
|
+
var byOrder = (left, right) => (left.order ?? 0) - (right.order ?? 0) || left.id.localeCompare(right.id);
|
|
316
|
+
var composeCampUiDetailBlocks = (defaults, contributions) => {
|
|
317
|
+
const renderers = [...defaults, ...contributions];
|
|
318
|
+
const ids = renderers.map((renderer) => renderer.id);
|
|
319
|
+
const duplicateId = ids.find((id, index) => ids.indexOf(id) !== index);
|
|
320
|
+
return duplicateId === void 0 ? Effect_exports.succeed(renderers.sort(byOrder)) : Effect_exports.fail(
|
|
321
|
+
new CampUiCompositionError({
|
|
322
|
+
code: "duplicate_id",
|
|
323
|
+
details: { area: "detail-block", id: duplicateId }
|
|
324
|
+
})
|
|
325
|
+
);
|
|
326
|
+
};
|
|
327
|
+
var renderCampUiDetailBlocks = (registry, context) => renderCampUiDetailBlockLayout(registry, context).lines;
|
|
328
|
+
var renderCampUiDetailBlockLayout = (registry, context) => {
|
|
329
|
+
const lines = [];
|
|
330
|
+
const ranges = [];
|
|
331
|
+
for (const renderer of registry) {
|
|
332
|
+
for (const block of renderer.render(context)) {
|
|
333
|
+
const start = lines.length;
|
|
334
|
+
lines.push(...block.lines, "");
|
|
335
|
+
ranges.push({
|
|
336
|
+
id: block.id,
|
|
337
|
+
start,
|
|
338
|
+
end: start + block.lines.length,
|
|
339
|
+
toggleable: block.toggleable === true
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return { lines, ranges };
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// packages/host-ui/src/model/campUiAttention.ts
|
|
347
|
+
var campUiPendingAskTargets = (snapshot) => snapshot.workers.flatMap(
|
|
348
|
+
(worker, workerIndex) => worker.asks.filter((ask) => ask.state === CampAskState.pending).map((ask) => ({
|
|
349
|
+
askId: ask.askId,
|
|
350
|
+
blockId: campUiAskBlockId(ask.askId),
|
|
351
|
+
openedAt: ask.openedAt,
|
|
352
|
+
workerId: worker.snapshot.worker.id,
|
|
353
|
+
workerIndex
|
|
354
|
+
}))
|
|
355
|
+
).sort(
|
|
356
|
+
(left, right) => left.openedAt - right.openedAt || left.workerIndex - right.workerIndex || left.askId.localeCompare(right.askId)
|
|
357
|
+
);
|
|
358
|
+
var nextCampUiPendingAskTarget = (snapshot, focusedBlockId) => {
|
|
359
|
+
const targets = campUiPendingAskTargets(snapshot);
|
|
360
|
+
if (targets.length === 0) return void 0;
|
|
361
|
+
const current = targets.findIndex((target) => target.blockId === focusedBlockId);
|
|
362
|
+
return targets[current < 0 ? 0 : (current + 1) % targets.length];
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
// packages/host-ui/src/interaction/campUiIntents.ts
|
|
366
|
+
var CampUiIntents = Data_exports.taggedEnum();
|
|
367
|
+
var makeCampUiIntentHandler = (options) => Match_exports.type().pipe(
|
|
368
|
+
Match_exports.tagsExhaustive({
|
|
369
|
+
ActivateScreen: ({ screen }) => options.controller.activateScreen(screen),
|
|
370
|
+
FocusDetailBlock: ({ blockId }) => options.controller.focusDetailBlock(blockId),
|
|
371
|
+
FocusSlot: ({ slotId }) => options.controller.focusSlot(slotId),
|
|
372
|
+
JumpToWorkerTail: ({ workerId }) => options.controller.focusDetailBlock(void 0).pipe(Effect_exports.zipRight(options.controller.setScrollOffset(workerId, 0))),
|
|
373
|
+
NextPendingAsk: () => Effect_exports.suspend(() => {
|
|
374
|
+
const model = options.readModel();
|
|
375
|
+
const target = nextCampUiPendingAskTarget(
|
|
376
|
+
model.snapshot,
|
|
377
|
+
model.state.focusedDetailBlockId
|
|
378
|
+
);
|
|
379
|
+
return target === void 0 ? Effect_exports.void : options.controller.openWorker(target.workerId).pipe(
|
|
380
|
+
Effect_exports.zipRight(options.controller.setDetailBlockExpanded(target.blockId, true)),
|
|
381
|
+
Effect_exports.zipRight(options.controller.focusDetailBlock(target.blockId)),
|
|
382
|
+
Effect_exports.zipRight(options.controller.focusSlot(CampUiSlot.transcript))
|
|
383
|
+
);
|
|
384
|
+
}),
|
|
385
|
+
OpenWorker: ({ workerId }) => options.controller.openWorker(workerId),
|
|
386
|
+
ScrollWorker: ({ workerId, delta }) => Effect_exports.suspend(() => {
|
|
387
|
+
const offset = options.readModel().state.scrollOffsets.get(workerId) ?? 0;
|
|
388
|
+
return options.controller.focusDetailBlock(void 0).pipe(
|
|
389
|
+
Effect_exports.zipRight(
|
|
390
|
+
options.controller.setScrollOffset(workerId, Math.max(0, offset + delta))
|
|
391
|
+
)
|
|
392
|
+
);
|
|
393
|
+
}),
|
|
394
|
+
SelectRelativeWorker: ({ delta }) => Effect_exports.suspend(() => {
|
|
395
|
+
const model = options.readModel();
|
|
396
|
+
const workers = model.snapshot.workers;
|
|
397
|
+
if (workers.length === 0) return Effect_exports.void;
|
|
398
|
+
const current = workers.findIndex(
|
|
399
|
+
(worker) => worker.snapshot.worker.id === model.state.selectedWorkerId
|
|
400
|
+
);
|
|
401
|
+
const index = current < 0 ? 0 : Math.min(workers.length - 1, Math.max(0, current + delta));
|
|
402
|
+
const workerId = workers[index]?.snapshot.worker.id;
|
|
403
|
+
return workerId === void 0 ? Effect_exports.void : options.controller.selectWorker(workerId);
|
|
404
|
+
}),
|
|
405
|
+
SelectWorker: ({ workerId }) => options.controller.selectWorker(workerId),
|
|
406
|
+
SetDraft: ({ workerId, draft }) => options.controller.setDraft(workerId, draft),
|
|
407
|
+
SetScrollOffset: ({ workerId, offset }) => options.controller.setScrollOffset(workerId, offset),
|
|
408
|
+
SetTranscriptVisible: ({ visible }) => options.controller.setDetailVisible(visible),
|
|
409
|
+
Steer: ({ workerId, message }) => message.trim() === "" ? Effect_exports.void : options.backend.steer({ workerId, message }).pipe(
|
|
410
|
+
Effect_exports.tap(() => options.controller.setActionError(void 0)),
|
|
411
|
+
Effect_exports.tap(() => options.controller.setDraft(workerId, "")),
|
|
412
|
+
Effect_exports.catchAll(
|
|
413
|
+
(cause) => options.controller.setDraft(workerId, message).pipe(Effect_exports.zipRight(options.controller.setActionError(cause.message)))
|
|
414
|
+
)
|
|
415
|
+
),
|
|
416
|
+
ToggleDetailBlock: ({ blockId }) => Effect_exports.suspend(
|
|
417
|
+
() => options.controller.setDetailBlockExpanded(
|
|
418
|
+
blockId,
|
|
419
|
+
!options.readModel().state.expandedDetailBlockIds.has(blockId)
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
})
|
|
423
|
+
);
|
|
424
|
+
var makeCampUiIntentRouter = (options) => {
|
|
425
|
+
const handle = makeCampUiIntentHandler(options);
|
|
426
|
+
return { emit: (intent) => options.dispatch(handle(intent)) };
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
// packages/host-ui/src/interaction/campUiSpatialFocus.ts
|
|
430
|
+
var CampUiFocusDirection = {
|
|
431
|
+
down: "down",
|
|
432
|
+
left: "left",
|
|
433
|
+
right: "right",
|
|
434
|
+
up: "up"
|
|
435
|
+
};
|
|
436
|
+
var centerX = (region2) => region2.x + region2.width / 2;
|
|
437
|
+
var centerY = (region2) => region2.y + region2.height / 2;
|
|
438
|
+
var distanceToInterval = (value, start, end) => value < start ? start - value : value > end ? value - end : 0;
|
|
439
|
+
var scoreCandidate = (current, candidate, direction, order) => {
|
|
440
|
+
const horizontalDelta = centerX(candidate) - centerX(current);
|
|
441
|
+
const verticalDelta = centerY(candidate) - centerY(current);
|
|
442
|
+
const horizontal = direction === CampUiFocusDirection.left || direction === CampUiFocusDirection.right;
|
|
443
|
+
const directionDelta = horizontal ? horizontalDelta : verticalDelta;
|
|
444
|
+
const expectedSign = direction === CampUiFocusDirection.right || direction === CampUiFocusDirection.down ? 1 : -1;
|
|
445
|
+
if (directionDelta * expectedSign <= 0) return void 0;
|
|
446
|
+
const primaryDistance = horizontal ? direction === CampUiFocusDirection.right ? Math.max(0, candidate.x - (current.x + current.width)) : Math.max(0, current.x - (candidate.x + candidate.width)) : direction === CampUiFocusDirection.down ? Math.max(0, candidate.y - (current.y + current.height)) : Math.max(0, current.y - (candidate.y + candidate.height));
|
|
447
|
+
const perpendicularDistance = horizontal ? distanceToInterval(centerY(current), candidate.y, candidate.y + candidate.height) : distanceToInterval(centerX(current), candidate.x, candidate.x + candidate.width);
|
|
448
|
+
return {
|
|
449
|
+
slotId: candidate.slotId,
|
|
450
|
+
primaryDistance,
|
|
451
|
+
perpendicularDistance,
|
|
452
|
+
centerDistance: Math.abs(directionDelta),
|
|
453
|
+
order
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
var compareCandidates = (left, right) => left.primaryDistance - right.primaryDistance || left.perpendicularDistance - right.perpendicularDistance || left.centerDistance - right.centerDistance || left.order - right.order;
|
|
457
|
+
var findCampUiSpatialFocusTarget = (frame, currentSlotId, focusableSlotIds, direction) => {
|
|
458
|
+
if (!focusableSlotIds.has(currentSlotId)) return void 0;
|
|
459
|
+
const current = frame?.regions.find((region2) => region2.slotId === currentSlotId);
|
|
460
|
+
if (current === void 0) return void 0;
|
|
461
|
+
return frame?.regions.map(
|
|
462
|
+
(candidate, order) => candidate.slotId === currentSlotId || !focusableSlotIds.has(candidate.slotId) ? void 0 : scoreCandidate(current, candidate, direction, order)
|
|
463
|
+
).filter((candidate) => candidate !== void 0).sort(compareCandidates)[0]?.slotId;
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
// packages/host-ui/src/layout/campUiLayoutPolicy.ts
|
|
467
|
+
var defaultCampUiLayoutPolicy = {
|
|
468
|
+
leftMinimumWidth: 28,
|
|
469
|
+
leftMaximumWidth: 40,
|
|
470
|
+
sessionMinimumWidth: 52,
|
|
471
|
+
wideMinimumHeight: 18,
|
|
472
|
+
leftRatio: 0.3,
|
|
473
|
+
footerHeight: 3,
|
|
474
|
+
sessionHeaderHeight: 1,
|
|
475
|
+
composerHeight: 3
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
// packages/host-ui/src/layout/campUiSizing.ts
|
|
479
|
+
var CampUiSectionSizeKind = {
|
|
480
|
+
fixed: "fixed",
|
|
481
|
+
grow: "grow"
|
|
482
|
+
};
|
|
483
|
+
var rows = (value) => Math.max(0, Math.floor(value));
|
|
484
|
+
var allocateCampUiSectionRows = (availableRows, sizes) => {
|
|
485
|
+
const allocated = sizes.map(() => 0);
|
|
486
|
+
let remaining = rows(availableRows);
|
|
487
|
+
const growIndexes = [];
|
|
488
|
+
for (const [index, size] of sizes.entries()) {
|
|
489
|
+
if (size.kind === CampUiSectionSizeKind.grow) {
|
|
490
|
+
growIndexes.push(index);
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const height = Math.min(remaining, rows(size.rows));
|
|
494
|
+
allocated[index] = height;
|
|
495
|
+
remaining -= height;
|
|
496
|
+
}
|
|
497
|
+
const maximumMinimum = Math.max(
|
|
498
|
+
0,
|
|
499
|
+
...growIndexes.map((index) => {
|
|
500
|
+
const size = sizes[index];
|
|
501
|
+
return size?.kind === CampUiSectionSizeKind.grow ? rows(size.minimumRows ?? 0) : 0;
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
for (let minimumRow = 0; minimumRow < maximumMinimum && remaining > 0; minimumRow += 1) {
|
|
505
|
+
for (const index of growIndexes) {
|
|
506
|
+
const size = sizes[index];
|
|
507
|
+
if (size?.kind !== CampUiSectionSizeKind.grow || minimumRow >= rows(size.minimumRows ?? 0)) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
510
|
+
allocated[index] = (allocated[index] ?? 0) + 1;
|
|
511
|
+
remaining -= 1;
|
|
512
|
+
if (remaining === 0) break;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
let remainingWeight = growIndexes.reduce((total, index) => {
|
|
516
|
+
const size = sizes[index];
|
|
517
|
+
return total + (size?.kind === CampUiSectionSizeKind.grow ? Math.max(0, size.weight) : 0);
|
|
518
|
+
}, 0);
|
|
519
|
+
for (const [position, index] of growIndexes.entries()) {
|
|
520
|
+
if (remaining === 0) break;
|
|
521
|
+
const size = sizes[index];
|
|
522
|
+
if (size?.kind !== CampUiSectionSizeKind.grow) continue;
|
|
523
|
+
const weight = Math.max(0, size.weight);
|
|
524
|
+
const last = position === growIndexes.length - 1;
|
|
525
|
+
const height = last || remainingWeight <= 0 ? remaining : Math.floor(remaining * weight / remainingWeight);
|
|
526
|
+
allocated[index] = (allocated[index] ?? 0) + height;
|
|
527
|
+
remaining -= height;
|
|
528
|
+
remainingWeight -= weight;
|
|
529
|
+
}
|
|
530
|
+
return allocated;
|
|
531
|
+
};
|
|
532
|
+
var defaultCampUiLeftSectionSizes = {
|
|
533
|
+
camp: { kind: CampUiSectionSizeKind.fixed, rows: 4 },
|
|
534
|
+
resources: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 1 },
|
|
535
|
+
workers: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 1 }
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// packages/host-ui/src/layout/campUiLayout.ts
|
|
539
|
+
var CampUiLayoutMode = {
|
|
540
|
+
wide: "wide",
|
|
541
|
+
narrow: "narrow"
|
|
542
|
+
};
|
|
543
|
+
var region = (slotId, x, y, width, height) => width <= 0 || height <= 0 ? void 0 : { slotId, x, y, width, height };
|
|
544
|
+
var compact = (items) => items.filter((item) => item !== void 0);
|
|
545
|
+
var fixedRows = (available, requested) => Math.min(Math.max(available, 0), Math.max(requested, 0));
|
|
546
|
+
var leftSectionHeights = (bodyHeight, slots) => {
|
|
547
|
+
const size = (slotId, fallback) => slots?.get(slotId)?.sectionSize ?? fallback;
|
|
548
|
+
return allocateCampUiSectionRows(bodyHeight, [
|
|
549
|
+
size(CampUiSlot.campHeader, defaultCampUiLeftSectionSizes.camp),
|
|
550
|
+
size(CampUiSlot.resources, defaultCampUiLeftSectionSizes.resources),
|
|
551
|
+
size(CampUiSlot.roster, defaultCampUiLeftSectionSizes.workers)
|
|
552
|
+
]);
|
|
553
|
+
};
|
|
554
|
+
var makeNarrowFrame = (viewport, state, policy, slots) => {
|
|
555
|
+
const footerHeight = fixedRows(viewport.height, policy.footerHeight);
|
|
556
|
+
const bodyHeight = Math.max(0, viewport.height - footerHeight);
|
|
557
|
+
const footerY = bodyHeight;
|
|
558
|
+
const workerScreen = state.screen === CampUiScreen.worker && state.selectedWorkerId !== void 0;
|
|
559
|
+
const [campHeight = 0, resourcesHeight = 0, rosterHeight = 0] = workerScreen ? [] : leftSectionHeights(bodyHeight, slots);
|
|
560
|
+
const sessionHeaderHeight = workerScreen ? fixedRows(bodyHeight, policy.sessionHeaderHeight) : 0;
|
|
561
|
+
const composerHeight = workerScreen ? fixedRows(bodyHeight - sessionHeaderHeight, policy.composerHeight) : 0;
|
|
562
|
+
const transcriptHeight = workerScreen ? bodyHeight - sessionHeaderHeight - composerHeight : 0;
|
|
563
|
+
return {
|
|
564
|
+
mode: CampUiLayoutMode.narrow,
|
|
565
|
+
dividers: [],
|
|
566
|
+
regions: compact([
|
|
567
|
+
region(CampUiSlot.campHeader, 0, 0, viewport.width, campHeight),
|
|
568
|
+
region(CampUiSlot.resources, 0, campHeight, viewport.width, resourcesHeight),
|
|
569
|
+
region(CampUiSlot.roster, 0, campHeight + resourcesHeight, viewport.width, rosterHeight),
|
|
570
|
+
region(CampUiSlot.sessionHeader, 0, 0, viewport.width, sessionHeaderHeight),
|
|
571
|
+
region(CampUiSlot.transcript, 0, sessionHeaderHeight, viewport.width, transcriptHeight),
|
|
572
|
+
region(
|
|
573
|
+
CampUiSlot.composer,
|
|
574
|
+
0,
|
|
575
|
+
sessionHeaderHeight + transcriptHeight,
|
|
576
|
+
viewport.width,
|
|
577
|
+
composerHeight
|
|
578
|
+
),
|
|
579
|
+
region(
|
|
580
|
+
workerScreen ? CampUiSlot.sessionFooter : CampUiSlot.campFooter,
|
|
581
|
+
0,
|
|
582
|
+
footerY,
|
|
583
|
+
viewport.width,
|
|
584
|
+
footerHeight
|
|
585
|
+
)
|
|
586
|
+
])
|
|
587
|
+
};
|
|
588
|
+
};
|
|
589
|
+
var makeWideFrame = (viewport, policy, slots) => {
|
|
590
|
+
const footerHeight = fixedRows(viewport.height, policy.footerHeight);
|
|
591
|
+
const bodyHeight = Math.max(0, viewport.height - footerHeight);
|
|
592
|
+
const leftWidth = Math.max(
|
|
593
|
+
policy.leftMinimumWidth,
|
|
594
|
+
Math.min(
|
|
595
|
+
policy.leftMaximumWidth,
|
|
596
|
+
viewport.width - policy.sessionMinimumWidth - 1,
|
|
597
|
+
Math.floor(viewport.width * policy.leftRatio)
|
|
598
|
+
)
|
|
599
|
+
);
|
|
600
|
+
const rightX = leftWidth + 1;
|
|
601
|
+
const rightWidth = viewport.width - rightX;
|
|
602
|
+
const [campHeight = 0, resourcesHeight = 0, rosterHeight = 0] = leftSectionHeights(
|
|
603
|
+
bodyHeight,
|
|
604
|
+
slots
|
|
605
|
+
);
|
|
606
|
+
const sessionHeaderHeight = fixedRows(bodyHeight, policy.sessionHeaderHeight);
|
|
607
|
+
const composerHeight = fixedRows(bodyHeight - sessionHeaderHeight, policy.composerHeight);
|
|
608
|
+
const transcriptHeight = bodyHeight - sessionHeaderHeight - composerHeight;
|
|
609
|
+
const footerY = bodyHeight;
|
|
610
|
+
return {
|
|
611
|
+
mode: CampUiLayoutMode.wide,
|
|
612
|
+
dividers: viewport.height === 0 ? [] : [{ x: leftWidth, y: 0, height: viewport.height }],
|
|
613
|
+
regions: compact([
|
|
614
|
+
region(CampUiSlot.campHeader, 0, 0, leftWidth, campHeight),
|
|
615
|
+
region(CampUiSlot.resources, 0, campHeight, leftWidth, resourcesHeight),
|
|
616
|
+
region(CampUiSlot.roster, 0, campHeight + resourcesHeight, leftWidth, rosterHeight),
|
|
617
|
+
region(CampUiSlot.sessionHeader, rightX, 0, rightWidth, sessionHeaderHeight),
|
|
618
|
+
region(CampUiSlot.transcript, rightX, sessionHeaderHeight, rightWidth, transcriptHeight),
|
|
619
|
+
region(
|
|
620
|
+
CampUiSlot.composer,
|
|
621
|
+
rightX,
|
|
622
|
+
sessionHeaderHeight + transcriptHeight,
|
|
623
|
+
rightWidth,
|
|
624
|
+
composerHeight
|
|
625
|
+
),
|
|
626
|
+
region(CampUiSlot.campFooter, 0, footerY, leftWidth, footerHeight),
|
|
627
|
+
region(CampUiSlot.sessionFooter, rightX, footerY, rightWidth, footerHeight)
|
|
628
|
+
])
|
|
629
|
+
};
|
|
630
|
+
};
|
|
631
|
+
var makeDefaultCampUiLayout = (policy = defaultCampUiLayoutPolicy) => ({
|
|
632
|
+
id: "default",
|
|
633
|
+
resolve: (viewport, state, slots) => {
|
|
634
|
+
const normalized = {
|
|
635
|
+
width: Math.max(0, viewport.width),
|
|
636
|
+
height: Math.max(0, viewport.height)
|
|
637
|
+
};
|
|
638
|
+
const wideMinimumWidth = policy.leftMinimumWidth + 1 + policy.sessionMinimumWidth;
|
|
639
|
+
return normalized.width >= wideMinimumWidth && normalized.height >= policy.wideMinimumHeight ? makeWideFrame(normalized, policy, slots) : makeNarrowFrame(normalized, state, policy, slots);
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// packages/host-ui/src/model/campUiRuntimeHealth.ts
|
|
644
|
+
var CampUiRuntimeStatus = {
|
|
645
|
+
starting: "starting",
|
|
646
|
+
ready: "ready",
|
|
647
|
+
retrying: "retrying",
|
|
648
|
+
failed: "failed",
|
|
649
|
+
stopped: "stopped"
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// packages/host-ui/src/pi/shared/campUiVisual.ts
|
|
653
|
+
import { getSelectListTheme } from "@earendil-works/pi-coding-agent";
|
|
654
|
+
|
|
655
|
+
// packages/host-ui/src/pi/shared/campUiRenderText.ts
|
|
656
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
657
|
+
var piShellIntegrationMarkers = [
|
|
658
|
+
"\x1B]133;A\x07",
|
|
659
|
+
"\x1B]133;B\x07",
|
|
660
|
+
"\x1B]133;C\x07"
|
|
661
|
+
];
|
|
662
|
+
var stripPiShellIntegration = (line) => piShellIntegrationMarkers.reduce((current, marker) => current.replaceAll(marker, ""), line);
|
|
663
|
+
var hasCampUiTextContent = (line) => {
|
|
664
|
+
let index = 0;
|
|
665
|
+
while (index < line.length) {
|
|
666
|
+
if (line.charCodeAt(index) !== 27) {
|
|
667
|
+
if ((line[index] ?? "").trim() !== "") return true;
|
|
668
|
+
index += 1;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
const marker = line[index + 1];
|
|
672
|
+
if (marker === "[") {
|
|
673
|
+
index += 2;
|
|
674
|
+
while (index < line.length) {
|
|
675
|
+
const code = line.charCodeAt(index);
|
|
676
|
+
index += 1;
|
|
677
|
+
if (code >= 64 && code <= 126) break;
|
|
678
|
+
}
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (marker === "]") {
|
|
682
|
+
index += 2;
|
|
683
|
+
while (index < line.length) {
|
|
684
|
+
if (line.charCodeAt(index) === 7) {
|
|
685
|
+
index += 1;
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
if (line.charCodeAt(index) === 27 && line[index + 1] === "\\") {
|
|
689
|
+
index += 2;
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
index += 1;
|
|
693
|
+
}
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
index += Math.min(2, line.length - index);
|
|
697
|
+
}
|
|
698
|
+
return false;
|
|
699
|
+
};
|
|
700
|
+
var fitCampUiLine = (line, width) => {
|
|
701
|
+
if (width <= 0) return "";
|
|
702
|
+
const truncated = truncateToWidth(stripPiShellIntegration(line), width);
|
|
703
|
+
return `${truncated}${" ".repeat(Math.max(0, width - visibleWidth(truncated)))}`;
|
|
704
|
+
};
|
|
705
|
+
var fitCampUiLines = (lines, width, height) => Array.from(
|
|
706
|
+
{ length: Math.max(0, height) },
|
|
707
|
+
(_, index) => fitCampUiLine(lines[index] ?? "", width)
|
|
708
|
+
);
|
|
709
|
+
var joinCampUiColumns = (left, right, width) => {
|
|
710
|
+
if (width <= 0) return "";
|
|
711
|
+
const rightWidth = visibleWidth(right);
|
|
712
|
+
if (rightWidth >= width) return fitCampUiLine(right, width);
|
|
713
|
+
const leftWidth = Math.max(0, width - rightWidth - 2);
|
|
714
|
+
const fittedLeft = truncateToWidth(left, leftWidth);
|
|
715
|
+
const gap = " ".repeat(Math.max(1, width - visibleWidth(fittedLeft) - rightWidth));
|
|
716
|
+
return fitCampUiLine(`${fittedLeft}${gap}${right}`, width);
|
|
717
|
+
};
|
|
718
|
+
var jsonText = (value) => {
|
|
719
|
+
try {
|
|
720
|
+
return JSON.stringify(value, void 0, 2);
|
|
721
|
+
} catch {
|
|
722
|
+
return String(value);
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
var compactNumber = (value) => new Intl.NumberFormat("en", { notation: "compact", maximumFractionDigits: 1 }).format(value);
|
|
726
|
+
|
|
727
|
+
// packages/host-ui/src/pi/shared/campUiVisual.ts
|
|
728
|
+
var CampUiTone = {
|
|
729
|
+
success: "success",
|
|
730
|
+
active: "active",
|
|
731
|
+
attention: "attention",
|
|
732
|
+
error: "error",
|
|
733
|
+
external: "external",
|
|
734
|
+
info: "info",
|
|
735
|
+
neutral: "neutral"
|
|
736
|
+
};
|
|
737
|
+
var CampUiGlyph = {
|
|
738
|
+
success: "\u2713",
|
|
739
|
+
active: "\u25B8",
|
|
740
|
+
attention: "\u25B8",
|
|
741
|
+
error: "\u2717",
|
|
742
|
+
external: "\u21C4",
|
|
743
|
+
neutral: "\xB7",
|
|
744
|
+
live: "\u25CF"
|
|
745
|
+
};
|
|
746
|
+
var ansiByTone = {
|
|
747
|
+
success: "\x1B[32m",
|
|
748
|
+
active: "\x1B[32m",
|
|
749
|
+
attention: "\x1B[33m",
|
|
750
|
+
error: "\x1B[31m",
|
|
751
|
+
external: "\x1B[34m",
|
|
752
|
+
info: "\x1B[34m",
|
|
753
|
+
neutral: "\x1B[90m"
|
|
754
|
+
};
|
|
755
|
+
var glyphByTone = {
|
|
756
|
+
success: CampUiGlyph.success,
|
|
757
|
+
active: CampUiGlyph.active,
|
|
758
|
+
attention: CampUiGlyph.attention,
|
|
759
|
+
error: CampUiGlyph.error,
|
|
760
|
+
external: CampUiGlyph.external,
|
|
761
|
+
info: CampUiGlyph.active,
|
|
762
|
+
neutral: CampUiGlyph.neutral
|
|
763
|
+
};
|
|
764
|
+
var statePresentation = /* @__PURE__ */ new Map([
|
|
765
|
+
["pending", { tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }],
|
|
766
|
+
["starting", { tone: CampUiTone.active, glyph: CampUiGlyph.active }],
|
|
767
|
+
["running", { tone: CampUiTone.active, glyph: CampUiGlyph.active }],
|
|
768
|
+
["recovering", { tone: CampUiTone.attention, glyph: CampUiGlyph.attention }],
|
|
769
|
+
["completed", { tone: CampUiTone.success, glyph: CampUiGlyph.success }],
|
|
770
|
+
["failed", { tone: CampUiTone.error, glyph: CampUiGlyph.error }],
|
|
771
|
+
["closed", { tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }],
|
|
772
|
+
["ready", { tone: CampUiTone.success, glyph: CampUiGlyph.live }],
|
|
773
|
+
["degraded", { tone: CampUiTone.attention, glyph: CampUiGlyph.attention }],
|
|
774
|
+
["streaming", { tone: CampUiTone.active, glyph: CampUiGlyph.active }],
|
|
775
|
+
["idle", { tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }]
|
|
776
|
+
]);
|
|
777
|
+
var styleCampUiTone = (tone, text) => `${ansiByTone[tone]}${text}\x1B[39m`;
|
|
778
|
+
var uniqueCampUiNames = (names) => [...new Set(names)].sort((left, right) => left.localeCompare(right));
|
|
779
|
+
var renderCampUiStatus = (label, tone, glyph = glyphByTone[tone]) => styleCampUiTone(tone, `${glyph} ${label}`);
|
|
780
|
+
var renderCampUiState = (state) => {
|
|
781
|
+
const presentation = statePresentation.get(state) ?? {
|
|
782
|
+
tone: CampUiTone.neutral,
|
|
783
|
+
glyph: CampUiGlyph.neutral
|
|
784
|
+
};
|
|
785
|
+
return renderCampUiStatus(state, presentation.tone, presentation.glyph);
|
|
786
|
+
};
|
|
787
|
+
var workerStatePresentation = /* @__PURE__ */ new Map([
|
|
788
|
+
["queued", { label: "queued", tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }],
|
|
789
|
+
["starting", { label: "starting", tone: CampUiTone.info, glyph: CampUiGlyph.active }],
|
|
790
|
+
["running", { label: "working", tone: CampUiTone.active, glyph: CampUiGlyph.active }],
|
|
791
|
+
[
|
|
792
|
+
"waiting_for_orchestrator",
|
|
793
|
+
{ label: "ask", tone: CampUiTone.attention, glyph: CampUiGlyph.attention }
|
|
794
|
+
],
|
|
795
|
+
/* A bounded wait, not a failure: the session and the transcript are still there. */
|
|
796
|
+
["retrying", { label: "retrying", tone: CampUiTone.attention, glyph: CampUiGlyph.attention }],
|
|
797
|
+
["idle", { label: "idle", tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }],
|
|
798
|
+
["interrupted", { label: "interrupted", tone: CampUiTone.error, glyph: CampUiGlyph.error }],
|
|
799
|
+
["recovering", { label: "recovering", tone: CampUiTone.attention, glyph: CampUiGlyph.attention }],
|
|
800
|
+
["completed", { label: "done", tone: CampUiTone.success, glyph: CampUiGlyph.success }],
|
|
801
|
+
["failed", { label: "error", tone: CampUiTone.error, glyph: CampUiGlyph.error }],
|
|
802
|
+
["closed", { label: "closed", tone: CampUiTone.neutral, glyph: CampUiGlyph.neutral }]
|
|
803
|
+
]);
|
|
804
|
+
var fallbackWorkerPresentation = {
|
|
805
|
+
label: "unknown",
|
|
806
|
+
tone: CampUiTone.neutral,
|
|
807
|
+
glyph: CampUiGlyph.neutral
|
|
808
|
+
};
|
|
809
|
+
var renderCampUiWorkerState = (state, options) => {
|
|
810
|
+
const base = workerStatePresentation.get(state) ?? fallbackWorkerPresentation;
|
|
811
|
+
const terminal = state === "completed" || state === "closed";
|
|
812
|
+
const presentation = options.failed || state === "failed" || state === "interrupted" ? workerStatePresentation.get(state === "interrupted" ? "interrupted" : "failed") : !terminal && options.pendingAsks > 0 ? {
|
|
813
|
+
label: options.pendingAsks === 1 ? "ask" : `ask ${options.pendingAsks}`,
|
|
814
|
+
tone: CampUiTone.attention,
|
|
815
|
+
glyph: CampUiGlyph.attention
|
|
816
|
+
} : !terminal && options.streaming ? workerStatePresentation.get("running") : base;
|
|
817
|
+
const resolved = presentation ?? fallbackWorkerPresentation;
|
|
818
|
+
return renderCampUiStatus(resolved.label, resolved.tone, resolved.glyph);
|
|
819
|
+
};
|
|
820
|
+
var renderSelectedCampUiRow = (line, width) => {
|
|
821
|
+
const selected = getSelectListTheme().selectedText(fitCampUiLine(line, width));
|
|
822
|
+
return `\x1B[7m${selected}\x1B[27m`;
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
// packages/host-ui/src/pi/blocks/campUiDirectedBlock.ts
|
|
826
|
+
import { getMarkdownTheme, getSelectListTheme as getSelectListTheme2 } from "@earendil-works/pi-coding-agent";
|
|
827
|
+
import { Markdown, truncateToWidth as truncateToWidth2 } from "@earendil-works/pi-tui";
|
|
828
|
+
var DEFAULT_DIRECTED_BLOCK_BODY_LINES = 4;
|
|
829
|
+
var formatCampUiDuration = (durationMs) => {
|
|
830
|
+
const totalSeconds = Math.max(0, Math.floor(durationMs / 1e3));
|
|
831
|
+
const seconds = totalSeconds % 60;
|
|
832
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
833
|
+
if (totalMinutes === 0) return `${seconds}s`;
|
|
834
|
+
const minutes = totalMinutes % 60;
|
|
835
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
836
|
+
if (hours === 0) return `${minutes}m ${String(seconds).padStart(2, "0")}s`;
|
|
837
|
+
return `${hours}h ${String(minutes).padStart(2, "0")}m`;
|
|
838
|
+
};
|
|
839
|
+
var directedLine = (content, width, tone) => {
|
|
840
|
+
if (width <= 0) return "";
|
|
841
|
+
const border = styleCampUiTone(tone, "\u2502");
|
|
842
|
+
return width === 1 ? border : fitCampUiLine(`${border} ${content}`, width);
|
|
843
|
+
};
|
|
844
|
+
var collapseMarkdownLines = (lines, maximum, width) => {
|
|
845
|
+
if (lines.length <= maximum) return { lines, hidden: 0 };
|
|
846
|
+
if (maximum <= 0) return { lines: [], hidden: lines.length };
|
|
847
|
+
const visible = lines.slice(0, maximum);
|
|
848
|
+
const last = visible.at(-1);
|
|
849
|
+
if (last !== void 0) {
|
|
850
|
+
const previewWidth = Math.max(1, Math.floor(width * 0.7));
|
|
851
|
+
visible[visible.length - 1] = getSelectListTheme2().description(
|
|
852
|
+
`${truncateToWidth2(last, Math.max(1, previewWidth - 1))}\u2026`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
return { lines: visible, hidden: lines.length - maximum };
|
|
856
|
+
};
|
|
857
|
+
var renderCampUiDirectedBlock = (block, width) => {
|
|
858
|
+
if (width <= 0) return [];
|
|
859
|
+
const contentWidth = Math.max(1, width - 2);
|
|
860
|
+
const markdownLines = new Markdown(block.markdown, 0, 0, getMarkdownTheme()).render(contentWidth);
|
|
861
|
+
const maximum = block.collapsedBodyLines ?? DEFAULT_DIRECTED_BLOCK_BODY_LINES;
|
|
862
|
+
const collapsed = collapseMarkdownLines(markdownLines, maximum, contentWidth);
|
|
863
|
+
const body = block.expanded ? markdownLines : collapsed.lines;
|
|
864
|
+
const expandable = markdownLines.length > maximum;
|
|
865
|
+
const hint = !expandable || block.showToggleHint === false ? [] : [
|
|
866
|
+
getSelectListTheme2().description(
|
|
867
|
+
block.expanded ? "enter collapse" : `enter expand \xB7 ${collapsed.hidden} lines hidden`
|
|
868
|
+
)
|
|
869
|
+
];
|
|
870
|
+
return [
|
|
871
|
+
directedLine(renderCampUiStatus(block.label, block.tone), width, block.tone),
|
|
872
|
+
...body.map((line) => directedLine(line, width, block.tone)),
|
|
873
|
+
directedLine(getSelectListTheme2().description(block.summary), width, block.tone),
|
|
874
|
+
...hint.map((line) => directedLine(line, width, block.tone))
|
|
875
|
+
];
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// packages/host-ui/src/pi/blocks/campUiAskBlock.ts
|
|
879
|
+
var toneByState = {
|
|
880
|
+
[CampAskState.pending]: CampUiTone.attention,
|
|
881
|
+
[CampAskState.answered]: CampUiTone.success,
|
|
882
|
+
[CampAskState.expired]: CampUiTone.error,
|
|
883
|
+
[CampAskState.interrupted]: CampUiTone.error
|
|
884
|
+
};
|
|
885
|
+
var campUiPendingAskBlockRenderer = {
|
|
886
|
+
id: "pending-asks",
|
|
887
|
+
order: 100,
|
|
888
|
+
render: ({ model, worker, width }) => {
|
|
889
|
+
return worker.asks.map((ask) => {
|
|
890
|
+
const blockId = campUiAskBlockId(ask.askId);
|
|
891
|
+
const mode = ask.blocking ? "blocking" : "non-blocking";
|
|
892
|
+
const stateLabel = {
|
|
893
|
+
[CampAskState.pending]: `pending \xB7 ${formatCampUiDuration(
|
|
894
|
+
model.state.presentationNow - ask.openedAt
|
|
895
|
+
)}`,
|
|
896
|
+
[CampAskState.answered]: "answered",
|
|
897
|
+
[CampAskState.expired]: "expired",
|
|
898
|
+
[CampAskState.interrupted]: "interrupted"
|
|
899
|
+
}[ask.state];
|
|
900
|
+
const summary = {
|
|
901
|
+
[CampAskState.pending]: `Awaiting orchestrator reply \xB7 ${ask.askId}`,
|
|
902
|
+
[CampAskState.answered]: `${ask.answerSummary ?? "Answered"} \xB7 ${ask.askId}`,
|
|
903
|
+
[CampAskState.expired]: `Expired \xB7 ${ask.askId}`,
|
|
904
|
+
[CampAskState.interrupted]: `Interrupted by host restart \xB7 ${ask.askId}`
|
|
905
|
+
}[ask.state];
|
|
906
|
+
return {
|
|
907
|
+
id: blockId,
|
|
908
|
+
lines: renderCampUiDirectedBlock(
|
|
909
|
+
{
|
|
910
|
+
label: `Ask \xB7 to ${ask.controllerId} \xB7 ${mode} \xB7 ${stateLabel}`,
|
|
911
|
+
markdown: ask.question,
|
|
912
|
+
summary,
|
|
913
|
+
tone: toneByState[ask.state],
|
|
914
|
+
expanded: model.state.expandedDetailBlockIds.has(blockId),
|
|
915
|
+
...ask.state === CampAskState.answered ? { collapsedBodyLines: 0, showToggleHint: false } : {}
|
|
916
|
+
},
|
|
917
|
+
width
|
|
918
|
+
),
|
|
919
|
+
toggleable: ask.state !== CampAskState.answered
|
|
920
|
+
};
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
// packages/host-ui/src/pi/blocks/campUiAssistantMessage.ts
|
|
926
|
+
import { getMarkdownTheme as getMarkdownTheme2, getSelectListTheme as getSelectListTheme3 } from "@earendil-works/pi-coding-agent";
|
|
927
|
+
import { Container, Markdown as Markdown2, Spacer, Text } from "@earendil-works/pi-tui";
|
|
928
|
+
var CampUiAssistantMessage = class extends Container {
|
|
929
|
+
constructor(message) {
|
|
930
|
+
super();
|
|
931
|
+
const markdownTheme = getMarkdownTheme2();
|
|
932
|
+
const selectTheme = getSelectListTheme3();
|
|
933
|
+
const visible = message.content.filter(
|
|
934
|
+
(content) => content.kind !== "tool-call" && content.kind !== "image"
|
|
935
|
+
);
|
|
936
|
+
if (visible.length > 0) this.addChild(new Spacer(1));
|
|
937
|
+
for (const [index, content] of visible.entries()) {
|
|
938
|
+
if (content.kind === "text" && content.text.trim() !== "") {
|
|
939
|
+
this.addChild(new Markdown2(content.text.trim(), 1, 0, markdownTheme));
|
|
940
|
+
}
|
|
941
|
+
if (content.kind === "thinking" && content.text.trim() !== "") {
|
|
942
|
+
this.addChild(
|
|
943
|
+
new Markdown2(content.text.trim(), 1, 0, markdownTheme, {
|
|
944
|
+
color: selectTheme.description,
|
|
945
|
+
italic: true
|
|
946
|
+
})
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
if (content.kind === "unknown") {
|
|
950
|
+
this.addChild(
|
|
951
|
+
new Markdown2(
|
|
952
|
+
`Unknown content
|
|
953
|
+
|
|
954
|
+
\`\`\`json
|
|
955
|
+
${jsonText(content.value)}
|
|
956
|
+
\`\`\``,
|
|
957
|
+
1,
|
|
958
|
+
0,
|
|
959
|
+
markdownTheme
|
|
960
|
+
)
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
if (index < visible.length - 1) this.addChild(new Spacer(1));
|
|
964
|
+
}
|
|
965
|
+
for (const content of message.content) {
|
|
966
|
+
if (content.kind === "image") {
|
|
967
|
+
this.addChild(new Text(selectTheme.description(`[Image: ${content.mimeType}]`), 1, 0));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
if (message.errorMessage !== void 0) {
|
|
971
|
+
this.addChild(new Spacer(1));
|
|
972
|
+
this.addChild(new Text(selectTheme.noMatch(`Error: ${message.errorMessage}`), 1, 0));
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// packages/host-ui/src/pi/blocks/campUiComposer.ts
|
|
978
|
+
import { getMarkdownTheme as getMarkdownTheme3, getSelectListTheme as getSelectListTheme4 } from "@earendil-works/pi-coding-agent";
|
|
979
|
+
import { Editor, Key, matchesKey } from "@earendil-works/pi-tui";
|
|
980
|
+
var makeCampUiComposerComponent = (context, tui) => {
|
|
981
|
+
const markdownTheme = getMarkdownTheme3();
|
|
982
|
+
const selectTheme = getSelectListTheme4();
|
|
983
|
+
const editors = /* @__PURE__ */ new Map();
|
|
984
|
+
const makeEditor = (workerId) => {
|
|
985
|
+
let handlingSubmit = false;
|
|
986
|
+
let syncing = false;
|
|
987
|
+
const editor = new Editor(
|
|
988
|
+
tui,
|
|
989
|
+
{ borderColor: markdownTheme.codeBlockBorder, selectList: getSelectListTheme4() },
|
|
990
|
+
{ paddingX: 1 }
|
|
991
|
+
);
|
|
992
|
+
editor.onChange = (draft) => {
|
|
993
|
+
if (!syncing && !(handlingSubmit && draft === "")) {
|
|
994
|
+
context.emit(CampUiIntents.SetDraft({ workerId, draft }));
|
|
995
|
+
}
|
|
996
|
+
};
|
|
997
|
+
editor.onSubmit = (text) => {
|
|
998
|
+
if (text.trim() === "") return;
|
|
999
|
+
editor.addToHistory(text);
|
|
1000
|
+
context.emit(CampUiIntents.Steer({ workerId, message: text }));
|
|
1001
|
+
};
|
|
1002
|
+
return {
|
|
1003
|
+
editor,
|
|
1004
|
+
handleInput: (input) => {
|
|
1005
|
+
handlingSubmit = matchesKey(input, Key.enter);
|
|
1006
|
+
editor.handleInput(input);
|
|
1007
|
+
handlingSubmit = false;
|
|
1008
|
+
},
|
|
1009
|
+
setDraft: (draft) => {
|
|
1010
|
+
if (draft === editor.getText()) return;
|
|
1011
|
+
syncing = true;
|
|
1012
|
+
editor.setText(draft);
|
|
1013
|
+
syncing = false;
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
};
|
|
1017
|
+
const selectedEditor = () => {
|
|
1018
|
+
const workerId = context.readModel().state.selectedWorkerId;
|
|
1019
|
+
if (workerId === void 0) return void 0;
|
|
1020
|
+
const existing = editors.get(workerId);
|
|
1021
|
+
if (existing !== void 0) return existing;
|
|
1022
|
+
const created = makeEditor(workerId);
|
|
1023
|
+
editors.set(workerId, created);
|
|
1024
|
+
return created;
|
|
1025
|
+
};
|
|
1026
|
+
return {
|
|
1027
|
+
focusable: true,
|
|
1028
|
+
render: (viewport) => {
|
|
1029
|
+
const model = context.readModel();
|
|
1030
|
+
const workerId = model.state.selectedWorkerId;
|
|
1031
|
+
const entry = selectedEditor();
|
|
1032
|
+
if (workerId === void 0 || entry === void 0) {
|
|
1033
|
+
return [selectTheme.description("worker not selected")];
|
|
1034
|
+
}
|
|
1035
|
+
const { editor } = entry;
|
|
1036
|
+
editor.focused = model.state.focusedSlotId === CampUiSlot.composer;
|
|
1037
|
+
editor.borderColor = editor.focused ? selectTheme.selectedPrefix : markdownTheme.codeBlockBorder;
|
|
1038
|
+
entry.setDraft(model.state.drafts.get(workerId) ?? "");
|
|
1039
|
+
return editor.render(viewport.width).slice(0, viewport.height);
|
|
1040
|
+
},
|
|
1041
|
+
handleInput: (input) => {
|
|
1042
|
+
const entry = selectedEditor();
|
|
1043
|
+
if (entry === void 0) return false;
|
|
1044
|
+
const { editor } = entry;
|
|
1045
|
+
const autocompleteVisible = editor.isShowingAutocomplete();
|
|
1046
|
+
if (matchesKey(input, Key.escape) || matchesKey(input, Key.tab) || matchesKey(input, "shift+tab")) {
|
|
1047
|
+
if (!autocompleteVisible) return false;
|
|
1048
|
+
entry.handleInput(input);
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
const arrow = matchesKey(input, Key.left) || matchesKey(input, Key.right) || matchesKey(input, Key.up) || matchesKey(input, Key.down);
|
|
1052
|
+
if (arrow) {
|
|
1053
|
+
const beforeCursor = editor.getCursor();
|
|
1054
|
+
const beforeText = editor.getText();
|
|
1055
|
+
entry.handleInput(input);
|
|
1056
|
+
const afterCursor = editor.getCursor();
|
|
1057
|
+
return autocompleteVisible || beforeText !== editor.getText() || beforeCursor.line !== afterCursor.line || beforeCursor.col !== afterCursor.col;
|
|
1058
|
+
}
|
|
1059
|
+
entry.handleInput(input);
|
|
1060
|
+
return true;
|
|
1061
|
+
},
|
|
1062
|
+
handlePointer: (pointer) => pointer.action === "press" && pointer.button === "left" ? [CampUiIntents.FocusSlot({ slotId: CampUiSlot.composer })] : [],
|
|
1063
|
+
invalidate: () => {
|
|
1064
|
+
for (const { editor } of editors.values()) editor.invalidate();
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
// packages/host-ui/src/pi/blocks/campUiDetail.ts
|
|
1070
|
+
import { getSelectListTheme as getSelectListTheme7 } from "@earendil-works/pi-coding-agent";
|
|
1071
|
+
import { Key as Key2, matchesKey as matchesKey2 } from "@earendil-works/pi-tui";
|
|
1072
|
+
|
|
1073
|
+
// packages/host-ui/src/pi/blocks/campUiEquipmentBlock.ts
|
|
1074
|
+
import { getMarkdownTheme as getMarkdownTheme4, UserMessageComponent } from "@earendil-works/pi-coding-agent";
|
|
1075
|
+
var escapeMarkdown = (value) => value.replaceAll(/([\\`*_[\]{}()#+.!|>])/gu, "\\$1");
|
|
1076
|
+
var renderNames = (names) => names.length === 0 ? "none" : names.map(escapeMarkdown).join(", ");
|
|
1077
|
+
var renderMcpServers = (equipment) => renderNames(
|
|
1078
|
+
equipment.mcp.serverNames.map((serverName) => {
|
|
1079
|
+
const toolNames = equipment.mcp.toolNamesByServer?.[serverName];
|
|
1080
|
+
return toolNames === void 0 ? serverName : `${serverName} (${toolNames.join(", ")})`;
|
|
1081
|
+
})
|
|
1082
|
+
);
|
|
1083
|
+
var equipmentMarkdown = (equipment) => {
|
|
1084
|
+
const role = equipment.role ?? equipment.preset ?? "worker";
|
|
1085
|
+
const tools = [.../* @__PURE__ */ new Set([...equipment.builtinTools, ...equipment.customToolNames])].sort();
|
|
1086
|
+
const lines = [
|
|
1087
|
+
`**Equipment \xB7 ${escapeMarkdown(role)}**`,
|
|
1088
|
+
"",
|
|
1089
|
+
`- **Tools:** ${renderNames(tools)}`,
|
|
1090
|
+
`- **MCP:** ${renderMcpServers(equipment)}`,
|
|
1091
|
+
`- **Skills:** ${renderNames(equipment.skillNames)}`,
|
|
1092
|
+
`- **Access:** ${equipment.readOnly ? "read-only" : "read/write"}`
|
|
1093
|
+
];
|
|
1094
|
+
return lines.join("\n");
|
|
1095
|
+
};
|
|
1096
|
+
var campUiEquipmentBlockRenderer = {
|
|
1097
|
+
id: "role-equipment",
|
|
1098
|
+
order: -100,
|
|
1099
|
+
render: ({ worker, width }) => [
|
|
1100
|
+
{
|
|
1101
|
+
id: "role-equipment",
|
|
1102
|
+
lines: new UserMessageComponent(
|
|
1103
|
+
equipmentMarkdown(worker.snapshot.worker.equipment),
|
|
1104
|
+
getMarkdownTheme4(),
|
|
1105
|
+
0
|
|
1106
|
+
).render(width)
|
|
1107
|
+
}
|
|
1108
|
+
]
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// packages/host-ui/src/pi/blocks/campUiTranscriptBlocks.ts
|
|
1112
|
+
import { getMarkdownTheme as getMarkdownTheme7, UserMessageComponent as UserMessageComponent2 } from "@earendil-works/pi-coding-agent";
|
|
1113
|
+
import { Markdown as Markdown4 } from "@earendil-works/pi-tui";
|
|
1114
|
+
|
|
1115
|
+
// packages/host-ui/src/pi/shared/campUiPanel.ts
|
|
1116
|
+
import { getMarkdownTheme as getMarkdownTheme5, getSelectListTheme as getSelectListTheme5 } from "@earendil-works/pi-coding-agent";
|
|
1117
|
+
import { truncateToWidth as truncateToWidth3, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
|
|
1118
|
+
var rule = (start, label, width, borderColor, labelColor) => {
|
|
1119
|
+
const prefix = `${borderColor(`${start}\u2500 `)}${labelColor(label)}${borderColor(" ")}`;
|
|
1120
|
+
const truncated = truncateToWidth3(prefix, width);
|
|
1121
|
+
return `${truncated}${borderColor("\u2500".repeat(Math.max(0, width - visibleWidth2(truncated))))}`;
|
|
1122
|
+
};
|
|
1123
|
+
var renderCampUiPanel = (label, body, width) => {
|
|
1124
|
+
if (width <= 0) return [];
|
|
1125
|
+
const borderColor = getMarkdownTheme5().codeBlockBorder;
|
|
1126
|
+
const labelColor = getSelectListTheme5().selectedText;
|
|
1127
|
+
if (width === 1) {
|
|
1128
|
+
return [borderColor("\u2502"), ...body.map(() => borderColor("\u2502")), borderColor("\u2570")];
|
|
1129
|
+
}
|
|
1130
|
+
return [
|
|
1131
|
+
rule("\u256D", label, width, borderColor, labelColor),
|
|
1132
|
+
...body.map((line) => fitCampUiLine(`${borderColor("\u2502")} ${line}`, width)),
|
|
1133
|
+
borderColor(`\u2570${"\u2500".repeat(width - 1)}`)
|
|
1134
|
+
];
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// packages/host-ui/src/pi/blocks/campUiToolResult.ts
|
|
1138
|
+
import {
|
|
1139
|
+
getMarkdownTheme as getMarkdownTheme6,
|
|
1140
|
+
getSelectListTheme as getSelectListTheme6,
|
|
1141
|
+
truncateToVisualLines
|
|
1142
|
+
} from "@earendil-works/pi-coding-agent";
|
|
1143
|
+
import { Markdown as Markdown3 } from "@earendil-works/pi-tui";
|
|
1144
|
+
var CAMP_UI_DEFAULT_TOOL_PREVIEW_LINES = 12;
|
|
1145
|
+
var CAMP_UI_BASH_PREVIEW_LINES = 8;
|
|
1146
|
+
var previewLinesByTool = /* @__PURE__ */ new Map([["bash", CAMP_UI_BASH_PREVIEW_LINES]]);
|
|
1147
|
+
var toolResultContent = (result, markdown) => result.content.flatMap((content) => {
|
|
1148
|
+
if (content.kind === "text" || content.kind === "thinking") return [content.text];
|
|
1149
|
+
if (content.kind === "image") return [`[Image: ${content.mimeType}]`];
|
|
1150
|
+
if (content.kind === "tool-call") {
|
|
1151
|
+
return [
|
|
1152
|
+
markdown ? `**Tool: ${content.toolName}**
|
|
1153
|
+
|
|
1154
|
+
\`\`\`json
|
|
1155
|
+
${jsonText(content.arguments)}
|
|
1156
|
+
\`\`\`` : jsonText(content.arguments)
|
|
1157
|
+
];
|
|
1158
|
+
}
|
|
1159
|
+
return [
|
|
1160
|
+
markdown ? `**Unknown content**
|
|
1161
|
+
|
|
1162
|
+
\`\`\`json
|
|
1163
|
+
${jsonText(content.value)}
|
|
1164
|
+
\`\`\`` : jsonText(content.value)
|
|
1165
|
+
];
|
|
1166
|
+
}).join(markdown ? "\n\n" : "\n");
|
|
1167
|
+
var toolResultPlainText = (result) => [
|
|
1168
|
+
toolResultContent(result, false),
|
|
1169
|
+
...result.errorMessage === void 0 ? [] : [`Error: ${result.errorMessage}`],
|
|
1170
|
+
...result.toolResultDetails === void 0 ? [] : [`Details
|
|
1171
|
+
${jsonText(result.toolResultDetails)}`]
|
|
1172
|
+
].filter((part) => part !== "").join("\n");
|
|
1173
|
+
var renderCampUiToolResult = (result, width, expanded) => {
|
|
1174
|
+
const boundedWidth = Math.max(1, width);
|
|
1175
|
+
const plainText = toolResultPlainText(result);
|
|
1176
|
+
const preview = truncateToVisualLines(
|
|
1177
|
+
getSelectListTheme6().description(plainText),
|
|
1178
|
+
previewLinesByTool.get(result.toolName ?? "") ?? CAMP_UI_DEFAULT_TOOL_PREVIEW_LINES,
|
|
1179
|
+
boundedWidth
|
|
1180
|
+
);
|
|
1181
|
+
if (!expanded) {
|
|
1182
|
+
return [
|
|
1183
|
+
...preview.visualLines,
|
|
1184
|
+
...preview.skippedCount === 0 ? [] : [
|
|
1185
|
+
getSelectListTheme6().description(
|
|
1186
|
+
`... ${preview.skippedCount} more lines \xB7 enter expand`
|
|
1187
|
+
)
|
|
1188
|
+
]
|
|
1189
|
+
];
|
|
1190
|
+
}
|
|
1191
|
+
const content = toolResultContent(result, true);
|
|
1192
|
+
const lines = content === "" ? [] : new Markdown3(content, 0, 0, getMarkdownTheme6()).render(boundedWidth);
|
|
1193
|
+
return [
|
|
1194
|
+
...lines,
|
|
1195
|
+
...result.errorMessage === void 0 ? [] : [getSelectListTheme6().noMatch(`Error: ${result.errorMessage}`)],
|
|
1196
|
+
...result.toolResultDetails === void 0 ? [] : [
|
|
1197
|
+
getSelectListTheme6().description("Details"),
|
|
1198
|
+
...new Markdown3(jsonText(result.toolResultDetails), 0, 0, getMarkdownTheme6()).render(
|
|
1199
|
+
boundedWidth
|
|
1200
|
+
)
|
|
1201
|
+
],
|
|
1202
|
+
...preview.skippedCount === 0 ? [] : [getSelectListTheme6().description("enter collapse")]
|
|
1203
|
+
];
|
|
1204
|
+
};
|
|
1205
|
+
|
|
1206
|
+
// packages/host-ui/src/pi/blocks/campUiTranscriptBlocks.ts
|
|
1207
|
+
var contentMarkdown = (message, includeToolCalls) => message.content.flatMap((content) => {
|
|
1208
|
+
if (content.kind === "text") return [content.text];
|
|
1209
|
+
if (content.kind === "thinking") {
|
|
1210
|
+
return [`> Thinking
|
|
1211
|
+
> ${content.text.replaceAll("\n", "\n> ")}`];
|
|
1212
|
+
}
|
|
1213
|
+
if (content.kind === "image") return [`[Image: ${content.mimeType}]`];
|
|
1214
|
+
if (content.kind === "tool-call") {
|
|
1215
|
+
return includeToolCalls ? [`**Tool: ${content.toolName}**
|
|
1216
|
+
|
|
1217
|
+
\`\`\`json
|
|
1218
|
+
${jsonText(content.arguments)}
|
|
1219
|
+
\`\`\``] : [];
|
|
1220
|
+
}
|
|
1221
|
+
return [`**Unknown content**
|
|
1222
|
+
|
|
1223
|
+
\`\`\`json
|
|
1224
|
+
${jsonText(content.value)}
|
|
1225
|
+
\`\`\``];
|
|
1226
|
+
}).join("\n\n");
|
|
1227
|
+
var renderMessage = (message, width) => {
|
|
1228
|
+
if (message.role === "user") {
|
|
1229
|
+
const markdown2 = contentMarkdown(message, false);
|
|
1230
|
+
if (markdown2 === "") return [];
|
|
1231
|
+
const attribution = message.source?.kind === "controller" ? [
|
|
1232
|
+
styleCampUiTone(
|
|
1233
|
+
CampUiTone.external,
|
|
1234
|
+
`controller ${message.source.controllerId}${message.source.actorId === void 0 ? "" : ` \xB7 ${message.source.actorId}`}`
|
|
1235
|
+
)
|
|
1236
|
+
] : [];
|
|
1237
|
+
return [
|
|
1238
|
+
...attribution,
|
|
1239
|
+
...new UserMessageComponent2(markdown2, getMarkdownTheme7(), 0).render(width)
|
|
1240
|
+
];
|
|
1241
|
+
}
|
|
1242
|
+
if (message.role === "assistant") {
|
|
1243
|
+
return new CampUiAssistantMessage(message).render(width);
|
|
1244
|
+
}
|
|
1245
|
+
const markdown = contentMarkdown(message, false);
|
|
1246
|
+
if (markdown === "") return [];
|
|
1247
|
+
const prefix = message.role === "assistant" ? "" : `${message.role}
|
|
1248
|
+
|
|
1249
|
+
`;
|
|
1250
|
+
const suffix = message.errorMessage === void 0 ? "" : `
|
|
1251
|
+
|
|
1252
|
+
Error: ${message.errorMessage}`;
|
|
1253
|
+
return new Markdown4(`${prefix}${markdown}${suffix}`, 0, 0, getMarkdownTheme7()).render(width);
|
|
1254
|
+
};
|
|
1255
|
+
var renderMarkdown = (markdown, width) => new Markdown4(markdown, 0, 0, getMarkdownTheme7()).render(Math.max(1, width));
|
|
1256
|
+
var firstStringArgument = (parameters, names) => {
|
|
1257
|
+
for (const name of names) {
|
|
1258
|
+
const value = parameters[name];
|
|
1259
|
+
if (typeof value === "string" && value.trim() !== "") return value;
|
|
1260
|
+
}
|
|
1261
|
+
return void 0;
|
|
1262
|
+
};
|
|
1263
|
+
var toolArgumentSummary = (toolName, parameters) => {
|
|
1264
|
+
const namesByTool = /* @__PURE__ */ new Map([
|
|
1265
|
+
["bash", ["command"]],
|
|
1266
|
+
["edit", ["path", "file_path"]],
|
|
1267
|
+
["find", ["pattern", "path"]],
|
|
1268
|
+
["grep", ["pattern", "path"]],
|
|
1269
|
+
["ls", ["path"]],
|
|
1270
|
+
["read", ["path", "file_path"]],
|
|
1271
|
+
["write", ["path", "file_path"]]
|
|
1272
|
+
]);
|
|
1273
|
+
return firstStringArgument(parameters, namesByTool.get(toolName) ?? []) ?? jsonText(parameters);
|
|
1274
|
+
};
|
|
1275
|
+
var renderToolCall = (toolCall, result, width, expanded) => {
|
|
1276
|
+
const innerWidth = Math.max(1, width - 2);
|
|
1277
|
+
const status = result === void 0 ? "running" : result.isError === true ? "failed" : "completed";
|
|
1278
|
+
const body = [
|
|
1279
|
+
...renderMarkdown(toolArgumentSummary(toolCall.toolName, toolCall.arguments), innerWidth),
|
|
1280
|
+
...result === void 0 ? [] : renderCampUiToolResult(result, innerWidth, expanded)
|
|
1281
|
+
];
|
|
1282
|
+
return renderCampUiPanel(`${toolCall.toolName} \xB7 ${status}`, body, width);
|
|
1283
|
+
};
|
|
1284
|
+
var renderUnmatchedToolResult = (message, width, expanded) => {
|
|
1285
|
+
const status = message.isError === true ? "failed" : "completed";
|
|
1286
|
+
const name = message.toolName ?? "unknown";
|
|
1287
|
+
return renderCampUiPanel(
|
|
1288
|
+
`${name} \xB7 ${status}`,
|
|
1289
|
+
renderCampUiToolResult(message, Math.max(1, width - 2), expanded),
|
|
1290
|
+
width
|
|
1291
|
+
);
|
|
1292
|
+
};
|
|
1293
|
+
var renderTranscript = (transcript, width, expandedDetailBlockIds) => {
|
|
1294
|
+
const results = /* @__PURE__ */ new Map();
|
|
1295
|
+
const callIds = /* @__PURE__ */ new Set();
|
|
1296
|
+
for (const message of transcript) {
|
|
1297
|
+
if (message.role === "toolResult" && message.toolCallId !== void 0) {
|
|
1298
|
+
results.set(message.toolCallId, message);
|
|
1299
|
+
}
|
|
1300
|
+
for (const content of message.content) {
|
|
1301
|
+
if (content.kind === "tool-call") callIds.add(content.toolCallId);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
return transcript.flatMap((message, index) => {
|
|
1305
|
+
const messageId = message.id ?? String(index);
|
|
1306
|
+
if (message.role === "toolResult") {
|
|
1307
|
+
const blockId = `tool-result:${messageId}`;
|
|
1308
|
+
return message.toolCallId !== void 0 && callIds.has(message.toolCallId) ? [] : [
|
|
1309
|
+
{
|
|
1310
|
+
id: blockId,
|
|
1311
|
+
lines: renderUnmatchedToolResult(message, width, expandedDetailBlockIds.has(blockId)),
|
|
1312
|
+
toggleable: true
|
|
1313
|
+
}
|
|
1314
|
+
];
|
|
1315
|
+
}
|
|
1316
|
+
const blocks = [];
|
|
1317
|
+
const messageLines = renderMessage(message, width);
|
|
1318
|
+
if (messageLines.length > 0) blocks.push({ id: `message:${messageId}`, lines: messageLines });
|
|
1319
|
+
for (const content of message.content) {
|
|
1320
|
+
if (content.kind !== "tool-call") continue;
|
|
1321
|
+
const blockId = `tool-call:${content.toolCallId}`;
|
|
1322
|
+
blocks.push({
|
|
1323
|
+
id: blockId,
|
|
1324
|
+
lines: renderToolCall(
|
|
1325
|
+
content,
|
|
1326
|
+
results.get(content.toolCallId),
|
|
1327
|
+
width,
|
|
1328
|
+
expandedDetailBlockIds.has(blockId)
|
|
1329
|
+
),
|
|
1330
|
+
toggleable: results.has(content.toolCallId)
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return blocks;
|
|
1334
|
+
});
|
|
1335
|
+
};
|
|
1336
|
+
var campUiTranscriptBlockRenderer = {
|
|
1337
|
+
id: "transcript",
|
|
1338
|
+
order: 0,
|
|
1339
|
+
render: ({ model, worker, width }) => renderTranscript(
|
|
1340
|
+
worker.snapshot.session?.transcript ?? [],
|
|
1341
|
+
width,
|
|
1342
|
+
model.state.expandedDetailBlockIds
|
|
1343
|
+
)
|
|
1344
|
+
};
|
|
1345
|
+
|
|
1346
|
+
// packages/host-ui/src/pi/blocks/campUiDetail.ts
|
|
1347
|
+
var defaultCampUiDetailBlockRenderers = [
|
|
1348
|
+
campUiEquipmentBlockRenderer,
|
|
1349
|
+
campUiTranscriptBlockRenderer,
|
|
1350
|
+
campUiPendingAskBlockRenderer
|
|
1351
|
+
];
|
|
1352
|
+
var sameCampUiDetailBlockSet = (left, right) => left.size === right.size && [...left].every((blockId) => right.has(blockId));
|
|
1353
|
+
var makeCampUiDetailComponent = (context, registry = defaultCampUiDetailBlockRenderers) => {
|
|
1354
|
+
const renderState = /* @__PURE__ */ new Map();
|
|
1355
|
+
const detailCache = /* @__PURE__ */ new Map();
|
|
1356
|
+
let renderedRanges = [];
|
|
1357
|
+
let visibleRanges = [];
|
|
1358
|
+
return {
|
|
1359
|
+
focusable: true,
|
|
1360
|
+
render: (viewport) => {
|
|
1361
|
+
renderedRanges = [];
|
|
1362
|
+
visibleRanges = [];
|
|
1363
|
+
const model = context.readModel();
|
|
1364
|
+
const selectTheme = getSelectListTheme7();
|
|
1365
|
+
const workerId = model.state.selectedWorkerId;
|
|
1366
|
+
if (workerId === void 0) return [selectTheme.description("No worker selected")];
|
|
1367
|
+
const worker = findCampUiWorker(model.snapshot, workerId);
|
|
1368
|
+
if (worker === void 0) return [selectTheme.noMatch("Selected worker is unavailable")];
|
|
1369
|
+
if (worker.error !== void 0) {
|
|
1370
|
+
return [selectTheme.noMatch(`Snapshot error: ${worker.error}`)];
|
|
1371
|
+
}
|
|
1372
|
+
const cached = detailCache.get(workerId);
|
|
1373
|
+
const detail = cached !== void 0 && cached.worker === worker && cached.width === viewport.width && cached.expandedDetailBlockIds === model.state.expandedDetailBlockIds && cached.presentationNow === model.state.presentationNow ? cached.detail : renderCampUiDetailBlockLayout(registry, {
|
|
1374
|
+
model,
|
|
1375
|
+
worker,
|
|
1376
|
+
width: viewport.width
|
|
1377
|
+
});
|
|
1378
|
+
if (detail !== cached?.detail) {
|
|
1379
|
+
detailCache.set(workerId, {
|
|
1380
|
+
worker,
|
|
1381
|
+
width: viewport.width,
|
|
1382
|
+
expandedDetailBlockIds: model.state.expandedDetailBlockIds,
|
|
1383
|
+
presentationNow: model.state.presentationNow,
|
|
1384
|
+
detail
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
renderedRanges = detail.ranges.filter((range) => range.toggleable);
|
|
1388
|
+
const focusedRange = model.state.focusedSlotId === CampUiSlot.transcript ? renderedRanges.find((range) => range.id === model.state.focusedDetailBlockId) : void 0;
|
|
1389
|
+
const focusedLine = focusedRange === void 0 ? void 0 : Array.from(
|
|
1390
|
+
{ length: focusedRange.end - focusedRange.start },
|
|
1391
|
+
(_, index) => focusedRange.start + index
|
|
1392
|
+
).find((index) => hasCampUiTextContent(detail.lines[index] ?? "")) ?? focusedRange.start;
|
|
1393
|
+
const lines = focusedRange === void 0 ? detail.lines : detail.lines.map(
|
|
1394
|
+
(line, index) => index === focusedLine ? fitCampUiLine(
|
|
1395
|
+
`${styleCampUiTone(CampUiTone.active, `${CampUiGlyph.active} `)}${line}`,
|
|
1396
|
+
viewport.width
|
|
1397
|
+
) : line
|
|
1398
|
+
);
|
|
1399
|
+
const previous = renderState.get(workerId);
|
|
1400
|
+
const offset = model.state.scrollOffsets.get(workerId) ?? 0;
|
|
1401
|
+
const growth = previous !== void 0 && previous.width === viewport.width && sameCampUiDetailBlockSet(
|
|
1402
|
+
previous.expandedDetailBlockIds,
|
|
1403
|
+
model.state.expandedDetailBlockIds
|
|
1404
|
+
) ? Math.max(0, lines.length - previous.lineCount) : 0;
|
|
1405
|
+
const focusedOffset = focusedRange === void 0 ? void 0 : Math.max(
|
|
1406
|
+
0,
|
|
1407
|
+
lines.length - Math.min(lines.length, focusedRange.start + viewport.height)
|
|
1408
|
+
);
|
|
1409
|
+
const maximumOffset = Math.max(0, lines.length - viewport.height);
|
|
1410
|
+
const desiredOffset = focusedOffset ?? (offset > 0 ? offset + growth : 0);
|
|
1411
|
+
const lockedOffset = Math.min(maximumOffset, desiredOffset);
|
|
1412
|
+
if (lockedOffset !== offset) {
|
|
1413
|
+
context.emit(CampUiIntents.SetScrollOffset({ workerId, offset: lockedOffset }));
|
|
1414
|
+
}
|
|
1415
|
+
renderState.set(workerId, {
|
|
1416
|
+
width: viewport.width,
|
|
1417
|
+
lineCount: lines.length,
|
|
1418
|
+
expandedDetailBlockIds: model.state.expandedDetailBlockIds
|
|
1419
|
+
});
|
|
1420
|
+
if (lines.length === 0) return [selectTheme.description("Waiting for worker output...")];
|
|
1421
|
+
const end = Math.max(0, lines.length - lockedOffset);
|
|
1422
|
+
const start = Math.max(0, end - viewport.height);
|
|
1423
|
+
visibleRanges = renderedRanges.filter((range) => range.end > start && range.start < end).map((range) => ({
|
|
1424
|
+
blockId: range.id,
|
|
1425
|
+
start: Math.max(0, range.start - start),
|
|
1426
|
+
end: Math.min(end - start, range.end - start)
|
|
1427
|
+
}));
|
|
1428
|
+
return lines.slice(start, end);
|
|
1429
|
+
},
|
|
1430
|
+
handleInput: (input) => {
|
|
1431
|
+
const delta = matchesKey2(input, Key2.up) ? -1 : matchesKey2(input, Key2.down) ? 1 : void 0;
|
|
1432
|
+
if (delta === void 0 || renderedRanges.length === 0) return false;
|
|
1433
|
+
const focusedBlockId = context.readModel().state.focusedDetailBlockId;
|
|
1434
|
+
const current = renderedRanges.findIndex((range) => range.id === focusedBlockId);
|
|
1435
|
+
const next = current < 0 ? delta < 0 ? renderedRanges.length - 1 : 0 : current + delta;
|
|
1436
|
+
const target = renderedRanges[next];
|
|
1437
|
+
if (target === void 0) return false;
|
|
1438
|
+
context.emit(CampUiIntents.FocusDetailBlock({ blockId: target.id }));
|
|
1439
|
+
return true;
|
|
1440
|
+
},
|
|
1441
|
+
handlePointer: (pointer) => {
|
|
1442
|
+
const workerId = context.readModel().state.selectedWorkerId;
|
|
1443
|
+
if (workerId === void 0) return [];
|
|
1444
|
+
if (pointer.action === "wheel") {
|
|
1445
|
+
return [
|
|
1446
|
+
CampUiIntents.FocusSlot({ slotId: CampUiSlot.transcript }),
|
|
1447
|
+
CampUiIntents.ScrollWorker({
|
|
1448
|
+
workerId,
|
|
1449
|
+
delta: pointer.direction === "up" ? 3 : -3
|
|
1450
|
+
})
|
|
1451
|
+
];
|
|
1452
|
+
}
|
|
1453
|
+
if (pointer.action !== "press" || pointer.button !== "left") return [];
|
|
1454
|
+
const target = visibleRanges.find(
|
|
1455
|
+
(range) => pointer.y >= range.start && pointer.y < range.end
|
|
1456
|
+
);
|
|
1457
|
+
return target === void 0 ? [CampUiIntents.FocusSlot({ slotId: CampUiSlot.transcript })] : [
|
|
1458
|
+
CampUiIntents.FocusSlot({ slotId: CampUiSlot.transcript }),
|
|
1459
|
+
CampUiIntents.FocusDetailBlock({ blockId: target.blockId }),
|
|
1460
|
+
CampUiIntents.ToggleDetailBlock({ blockId: target.blockId })
|
|
1461
|
+
];
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// packages/host-ui/src/pi/layout/campUiChrome.ts
|
|
1467
|
+
import { getSelectListTheme as getSelectListTheme8 } from "@earendil-works/pi-coding-agent";
|
|
1468
|
+
var makeCampUiCampHeader = (context) => ({
|
|
1469
|
+
render: (viewport) => {
|
|
1470
|
+
const snapshot = context.readModel().snapshot;
|
|
1471
|
+
const theme = getSelectListTheme8();
|
|
1472
|
+
const mode = snapshot.placement === "external" ? "agentless" : "foreman";
|
|
1473
|
+
return [
|
|
1474
|
+
fitCampUiLine(theme.description(snapshot.displayName), viewport.width),
|
|
1475
|
+
fitCampUiLine(
|
|
1476
|
+
`${theme.description(mode)} ${theme.description("\xB7")} ${renderCampUiState(snapshot.health.status)}`,
|
|
1477
|
+
viewport.width
|
|
1478
|
+
)
|
|
1479
|
+
];
|
|
1480
|
+
}
|
|
1481
|
+
});
|
|
1482
|
+
var makeCampUiSeat = (context) => ({
|
|
1483
|
+
render: (viewport) => {
|
|
1484
|
+
const snapshot = context.readModel().snapshot;
|
|
1485
|
+
const theme = getSelectListTheme8();
|
|
1486
|
+
const external = snapshot.placement === "external";
|
|
1487
|
+
const controller = snapshot.controllerConnected ? renderCampUiStatus("live", CampUiTone.success, CampUiGlyph.live) : renderCampUiStatus("lost", CampUiTone.error);
|
|
1488
|
+
return [
|
|
1489
|
+
fitCampUiLine(
|
|
1490
|
+
external ? `${renderCampUiStatus("controller", CampUiTone.external)} ${theme.description("\xB7 external \xB7")} ${controller}` : `${renderCampUiStatus("foreman", CampUiTone.neutral)} ${theme.description("\xB7 session not reported \xB7")} ${controller}`,
|
|
1491
|
+
viewport.width
|
|
1492
|
+
)
|
|
1493
|
+
];
|
|
1494
|
+
}
|
|
1495
|
+
});
|
|
1496
|
+
var makeCampUiRoster = (context) => ({
|
|
1497
|
+
focusable: true,
|
|
1498
|
+
render: (viewport) => {
|
|
1499
|
+
const model = context.readModel();
|
|
1500
|
+
const theme = getSelectListTheme8();
|
|
1501
|
+
const focused = model.state.focusedSlotId === CampUiSlot.roster;
|
|
1502
|
+
if (model.snapshot.workers.length === 0) {
|
|
1503
|
+
return [theme.description("No workers")];
|
|
1504
|
+
}
|
|
1505
|
+
const workers = model.snapshot.workers.map((entry) => {
|
|
1506
|
+
const worker = entry.snapshot.worker;
|
|
1507
|
+
const selected = worker.id === model.state.selectedWorkerId;
|
|
1508
|
+
const name = worker.alias ?? worker.id.slice(0, 8);
|
|
1509
|
+
const session = entry.snapshot.session;
|
|
1510
|
+
const active = uniqueCampUiNames([
|
|
1511
|
+
...session?.activeToolNames ?? [],
|
|
1512
|
+
...session?.pendingToolNames ?? []
|
|
1513
|
+
]);
|
|
1514
|
+
const pendingAsks = entry.asks.filter((ask) => ask.state === "pending").length;
|
|
1515
|
+
const tool = active.length === 0 ? "" : styleCampUiTone(CampUiTone.active, ` ${active.slice(0, 2).join(", ")}`);
|
|
1516
|
+
const label = selected ? theme.selectedText(name) : name;
|
|
1517
|
+
const role = theme.description(`(${worker.equipment.role ?? "worker"})`);
|
|
1518
|
+
const status = renderCampUiWorkerState(worker.state, {
|
|
1519
|
+
failed: entry.error !== void 0,
|
|
1520
|
+
pendingAsks,
|
|
1521
|
+
streaming: session?.isStreaming === true
|
|
1522
|
+
});
|
|
1523
|
+
const line = `${status} ${label} ${role}${tool}`;
|
|
1524
|
+
return selected && focused ? renderSelectedCampUiRow(line, viewport.width) : fitCampUiLine(line, viewport.width);
|
|
1525
|
+
});
|
|
1526
|
+
return workers;
|
|
1527
|
+
},
|
|
1528
|
+
handlePointer: (pointer) => {
|
|
1529
|
+
if (pointer.action !== "press" || pointer.button !== "left") return [];
|
|
1530
|
+
const workerId = context.readModel().snapshot.workers[pointer.y]?.snapshot.worker.id;
|
|
1531
|
+
if (workerId === void 0) return [];
|
|
1532
|
+
return [
|
|
1533
|
+
CampUiIntents.FocusSlot({ slotId: CampUiSlot.roster }),
|
|
1534
|
+
pointer.mode === "narrow" ? CampUiIntents.OpenWorker({ workerId }) : CampUiIntents.SelectWorker({ workerId })
|
|
1535
|
+
];
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
var makeCampUiSessionHeader = (context) => ({
|
|
1539
|
+
render: (viewport) => {
|
|
1540
|
+
const model = context.readModel();
|
|
1541
|
+
const theme = getSelectListTheme8();
|
|
1542
|
+
const workerId = model.state.selectedWorkerId;
|
|
1543
|
+
if (workerId === void 0) {
|
|
1544
|
+
return [fitCampUiLine(theme.description("Select a worker"), viewport.width)];
|
|
1545
|
+
}
|
|
1546
|
+
const worker = findCampUiWorker(model.snapshot, workerId);
|
|
1547
|
+
if (worker === void 0) return [theme.noMatch("Worker unavailable")];
|
|
1548
|
+
const workerName = worker.snapshot.worker.alias ?? workerId.slice(0, 8);
|
|
1549
|
+
const role = worker.snapshot.worker.equipment.role ?? "worker";
|
|
1550
|
+
const line = `${theme.selectedText(`camp / ${workerName}`)} ${theme.description(`\xB7 ${role} \xB7 session ${worker.snapshot.worker.sessionState}`)}`;
|
|
1551
|
+
return [fitCampUiLine(line, viewport.width)];
|
|
1552
|
+
},
|
|
1553
|
+
handlePointer: (pointer) => pointer.action === "press" && pointer.button === "left" && pointer.y === 0 ? [
|
|
1554
|
+
CampUiIntents.ActivateScreen({ screen: CampUiScreen.camp }),
|
|
1555
|
+
CampUiIntents.FocusDetailBlock({}),
|
|
1556
|
+
CampUiIntents.FocusSlot({ slotId: CampUiSlot.roster })
|
|
1557
|
+
] : []
|
|
1558
|
+
});
|
|
1559
|
+
|
|
1560
|
+
// packages/host-ui/src/pi/layout/campUiFooter.ts
|
|
1561
|
+
import { getMarkdownTheme as getMarkdownTheme8, getSelectListTheme as getSelectListTheme9 } from "@earendil-works/pi-coding-agent";
|
|
1562
|
+
var aggregateCampUsage = (workers) => {
|
|
1563
|
+
const usages = workers.flatMap(
|
|
1564
|
+
(worker) => worker.snapshot.session?.usage === void 0 ? [] : [worker.snapshot.session.usage]
|
|
1565
|
+
);
|
|
1566
|
+
return {
|
|
1567
|
+
cost: usages.reduce((total, usage) => total + usage.cost, 0),
|
|
1568
|
+
reportedWorkers: usages.length,
|
|
1569
|
+
tokens: usages.reduce((total, usage) => total + usage.tokens.total, 0),
|
|
1570
|
+
totalWorkers: workers.length
|
|
1571
|
+
};
|
|
1572
|
+
};
|
|
1573
|
+
var aggregateUsageText = (usage) => {
|
|
1574
|
+
if (usage.totalWorkers > 0 && usage.reportedWorkers === 0) {
|
|
1575
|
+
return "tokens unknown \xB7 cost unknown";
|
|
1576
|
+
}
|
|
1577
|
+
const partial = usage.reportedWorkers < usage.totalWorkers;
|
|
1578
|
+
const lowerBound = partial ? "\u2265" : "";
|
|
1579
|
+
const coverage = partial ? ` \xB7 ${usage.reportedWorkers}/${usage.totalWorkers} reported` : "";
|
|
1580
|
+
return `\u2193 ${lowerBound}${compactNumber(usage.tokens)} tokens \xB7 ${lowerBound}$${usage.cost.toFixed(3)}${coverage}`;
|
|
1581
|
+
};
|
|
1582
|
+
var footerDivider = (width) => fitCampUiLine(getMarkdownTheme8().codeBlockBorder("\u2500".repeat(width)), width);
|
|
1583
|
+
var retryText = (retryAt, now) => {
|
|
1584
|
+
if (retryAt === void 0) return "";
|
|
1585
|
+
return `retry in ${Math.max(0, Math.ceil((retryAt - now) / 1e3))}s`;
|
|
1586
|
+
};
|
|
1587
|
+
var diagnosticText = (value) => {
|
|
1588
|
+
const normalized = value?.replaceAll(/\s+/gu, " ").trim();
|
|
1589
|
+
return normalized === "" ? void 0 : normalized;
|
|
1590
|
+
};
|
|
1591
|
+
var makeCampUiCampFooter = (context) => ({
|
|
1592
|
+
render: (viewport) => {
|
|
1593
|
+
const model = context.readModel();
|
|
1594
|
+
const theme = getSelectListTheme9();
|
|
1595
|
+
const health = model.snapshot.health;
|
|
1596
|
+
const usage = aggregateUsageText(aggregateCampUsage(model.snapshot.workers));
|
|
1597
|
+
const actionError = diagnosticText(model.state.lastActionError);
|
|
1598
|
+
const healthSummary = diagnosticText(health.message);
|
|
1599
|
+
const healthMessage = health.code === void 0 ? healthSummary : healthSummary === void 0 ? health.code : `${health.code} \xB7 ${healthSummary}`;
|
|
1600
|
+
const healthTone = health.status === CampUiHealthStatus.failed ? CampUiTone.error : CampUiTone.attention;
|
|
1601
|
+
const diagnostic = actionError !== void 0 ? `${renderCampUiStatus("action error", CampUiTone.error)} ${theme.description(`\xB7 ${actionError}`)}` : healthMessage !== void 0 ? styleCampUiTone(healthTone, healthMessage) : health.status === CampUiHealthStatus.ready ? "" : renderCampUiStatus(health.status, healthTone);
|
|
1602
|
+
const retry = retryText(health.retryAt, model.state.presentationNow);
|
|
1603
|
+
return [
|
|
1604
|
+
footerDivider(viewport.width),
|
|
1605
|
+
joinCampUiColumns(diagnostic, theme.description(usage), viewport.width),
|
|
1606
|
+
fitCampUiLine(retry === "" ? "" : theme.description(retry), viewport.width)
|
|
1607
|
+
];
|
|
1608
|
+
}
|
|
1609
|
+
});
|
|
1610
|
+
var makeCampUiSessionFooter = (context) => ({
|
|
1611
|
+
render: (viewport) => {
|
|
1612
|
+
const model = context.readModel();
|
|
1613
|
+
const theme = getSelectListTheme9();
|
|
1614
|
+
const workerId = model.state.selectedWorkerId;
|
|
1615
|
+
const worker = workerId === void 0 ? void 0 : findCampUiWorker(model.snapshot, workerId);
|
|
1616
|
+
const usage = worker?.snapshot.session?.usage;
|
|
1617
|
+
const tokenText = usage === void 0 ? "tokens unknown" : `\u2193 ${compactNumber(usage.tokens.total)} tokens`;
|
|
1618
|
+
const costText = usage === void 0 ? "cost unknown" : `$${usage.cost.toFixed(3)}`;
|
|
1619
|
+
const cacheText = usage?.latestCacheHitRate === void 0 ? usage === void 0 || usage.tokens.total === 0 ? "cache n/a" : "cache not reported" : `cache ${usage.latestCacheHitRate.toFixed(0)}%`;
|
|
1620
|
+
const contextText = usage?.context?.percent === null || usage?.context?.percent === void 0 ? "context unknown" : `context ${usage.context.percent.toFixed(0)}%`;
|
|
1621
|
+
const modelText = usage?.model ?? worker?.snapshot.worker.equipment.model ?? "model unknown";
|
|
1622
|
+
const thinking = worker?.snapshot.session?.thinkingLevel ?? worker?.snapshot.worker.equipment.thinkingLevel ?? "unknown";
|
|
1623
|
+
const identity = worker === void 0 ? theme.description("worker not selected") : `${theme.selectedText(worker.snapshot.worker.alias ?? worker.snapshot.worker.id.slice(0, 8))} ${theme.description(`\xB7 ${worker.snapshot.worker.cwd}`)}`;
|
|
1624
|
+
const branch = worker === void 0 || model.snapshot.projectBranch === null ? "" : theme.description(`branch ${model.snapshot.projectBranch}`);
|
|
1625
|
+
return [
|
|
1626
|
+
footerDivider(viewport.width),
|
|
1627
|
+
branch === "" ? fitCampUiLine(identity, viewport.width) : joinCampUiColumns(identity, branch, viewport.width),
|
|
1628
|
+
joinCampUiColumns(
|
|
1629
|
+
theme.description(`${tokenText} \xB7 ${costText} \xB7 ${cacheText} \xB7 ${contextText}`),
|
|
1630
|
+
theme.description(`${modelText} \xB7 effort ${thinking}`),
|
|
1631
|
+
viewport.width
|
|
1632
|
+
)
|
|
1633
|
+
];
|
|
1634
|
+
}
|
|
1635
|
+
});
|
|
1636
|
+
|
|
1637
|
+
// packages/host-ui/src/pi/layout/campUiResources.ts
|
|
1638
|
+
import { getSelectListTheme as getSelectListTheme10 } from "@earendil-works/pi-coding-agent";
|
|
1639
|
+
var renderResourceTree = (label, value, tone, width) => {
|
|
1640
|
+
const theme = getSelectListTheme10();
|
|
1641
|
+
const lines = [
|
|
1642
|
+
theme.description(label),
|
|
1643
|
+
`${theme.description("\u2514\u2500")} ${renderCampUiStatus(value, tone)}`
|
|
1644
|
+
];
|
|
1645
|
+
return lines.map((line) => fitCampUiLine(line, width));
|
|
1646
|
+
};
|
|
1647
|
+
var makeCampUiResources = (context) => ({
|
|
1648
|
+
render: (viewport) => {
|
|
1649
|
+
const model = context.readModel();
|
|
1650
|
+
const snapshot = model.snapshot;
|
|
1651
|
+
const equipment = snapshot.workers.map((worker) => worker.snapshot.worker.equipment);
|
|
1652
|
+
const inventory = snapshot.resources;
|
|
1653
|
+
const resourcesReported = inventory.reportedWorkers > 0;
|
|
1654
|
+
const equipmentReported = equipment.length > 0;
|
|
1655
|
+
const partial = !inventory.complete;
|
|
1656
|
+
const resourceText = (items) => {
|
|
1657
|
+
if (!resourcesReported) return "not reported";
|
|
1658
|
+
const names = uniqueCampUiNames(items.map((item) => item.name));
|
|
1659
|
+
const value = names.length === 0 ? "none loaded" : names.join(", ");
|
|
1660
|
+
return partial ? `${value} \xB7 partial` : value;
|
|
1661
|
+
};
|
|
1662
|
+
const mcpNames = uniqueCampUiNames(equipment.flatMap((item) => item.mcp.serverNames));
|
|
1663
|
+
const mcpHealth = snapshot.workers.flatMap((worker) => worker.snapshot.mcp);
|
|
1664
|
+
const healthNames = new Set(mcpHealth.map((item) => item.serverName));
|
|
1665
|
+
const failedMcp = mcpHealth.some(
|
|
1666
|
+
(item) => item.status === "failed" || item.status === "closed"
|
|
1667
|
+
);
|
|
1668
|
+
const pendingMcp = mcpHealth.some(
|
|
1669
|
+
(item) => item.status === "connecting" || item.status === "retry-wait"
|
|
1670
|
+
);
|
|
1671
|
+
const partialMcp = mcpHealth.some((item) => !item.catalogComplete);
|
|
1672
|
+
const missingMcpHealth = mcpNames.some((name) => !healthNames.has(name));
|
|
1673
|
+
const mcpTone = failedMcp ? CampUiTone.error : pendingMcp || partialMcp || missingMcpHealth ? CampUiTone.attention : mcpNames.length > 0 ? CampUiTone.success : CampUiTone.neutral;
|
|
1674
|
+
const mcpNotes = [
|
|
1675
|
+
...failedMcp ? ["down"] : [],
|
|
1676
|
+
...pendingMcp ? ["connecting"] : [],
|
|
1677
|
+
...partialMcp ? ["catalog partial"] : [],
|
|
1678
|
+
...missingMcpHealth ? ["health not reported"] : []
|
|
1679
|
+
];
|
|
1680
|
+
const mcpText = !equipmentReported ? "not reported" : mcpNames.length === 0 ? "none configured" : `${mcpNames.join(", ")}${mcpNotes.length === 0 ? "" : ` \xB7 ${mcpNotes.join(", ")}`}`;
|
|
1681
|
+
return [
|
|
1682
|
+
...renderResourceTree(
|
|
1683
|
+
"Context",
|
|
1684
|
+
resourceText(inventory.contextFiles),
|
|
1685
|
+
inventory.contextFiles.length > 0 ? CampUiTone.success : CampUiTone.neutral,
|
|
1686
|
+
viewport.width
|
|
1687
|
+
),
|
|
1688
|
+
...renderResourceTree(
|
|
1689
|
+
"Extensions",
|
|
1690
|
+
resourceText(inventory.extensions),
|
|
1691
|
+
inventory.extensions.length > 0 ? CampUiTone.success : CampUiTone.neutral,
|
|
1692
|
+
viewport.width
|
|
1693
|
+
),
|
|
1694
|
+
...renderResourceTree(
|
|
1695
|
+
"Skills",
|
|
1696
|
+
resourceText(inventory.skills),
|
|
1697
|
+
inventory.skills.length > 0 ? CampUiTone.success : CampUiTone.neutral,
|
|
1698
|
+
viewport.width
|
|
1699
|
+
),
|
|
1700
|
+
...renderResourceTree("MCP", mcpText, mcpTone, viewport.width)
|
|
1701
|
+
];
|
|
1702
|
+
}
|
|
1703
|
+
});
|
|
1704
|
+
|
|
1705
|
+
// packages/host-ui/src/pi/layout/campUiDefaultSlots.ts
|
|
1706
|
+
var makeDefaultCampUiContributions = (tui, detailBlocks) => [
|
|
1707
|
+
{
|
|
1708
|
+
id: "default-camp-header",
|
|
1709
|
+
slotId: CampUiSlot.campHeader,
|
|
1710
|
+
mount: (context) => Effect_exports.succeed(makeCampUiCampHeader(context))
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
id: "default-seat",
|
|
1714
|
+
slotId: CampUiSlot.seat,
|
|
1715
|
+
mount: (context) => Effect_exports.succeed(makeCampUiSeat(context))
|
|
1716
|
+
},
|
|
1717
|
+
{
|
|
1718
|
+
id: "default-resources",
|
|
1719
|
+
slotId: CampUiSlot.resources,
|
|
1720
|
+
mount: (context) => Effect_exports.succeed(makeCampUiResources(context))
|
|
1721
|
+
},
|
|
1722
|
+
{
|
|
1723
|
+
id: "default-roster",
|
|
1724
|
+
slotId: CampUiSlot.roster,
|
|
1725
|
+
mount: (context) => Effect_exports.succeed(makeCampUiRoster(context))
|
|
1726
|
+
},
|
|
1727
|
+
{
|
|
1728
|
+
id: "default-session-header",
|
|
1729
|
+
slotId: CampUiSlot.sessionHeader,
|
|
1730
|
+
mount: (context) => Effect_exports.succeed(makeCampUiSessionHeader(context))
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
id: "default-transcript",
|
|
1734
|
+
slotId: CampUiSlot.transcript,
|
|
1735
|
+
mount: (context) => Effect_exports.succeed(makeCampUiDetailComponent(context, detailBlocks))
|
|
1736
|
+
},
|
|
1737
|
+
{
|
|
1738
|
+
id: "default-composer",
|
|
1739
|
+
slotId: CampUiSlot.composer,
|
|
1740
|
+
mount: (context) => Effect_exports.succeed(makeCampUiComposerComponent(context, tui))
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
id: "default-camp-footer",
|
|
1744
|
+
slotId: CampUiSlot.campFooter,
|
|
1745
|
+
mount: (context) => Effect_exports.succeed(makeCampUiCampFooter(context))
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
id: "default-session-footer",
|
|
1749
|
+
slotId: CampUiSlot.sessionFooter,
|
|
1750
|
+
mount: (context) => Effect_exports.succeed(makeCampUiSessionFooter(context))
|
|
1751
|
+
}
|
|
1752
|
+
];
|
|
1753
|
+
|
|
1754
|
+
// packages/host-ui/src/pi/layout/campUiSection.ts
|
|
1755
|
+
import { getMarkdownTheme as getMarkdownTheme9, getSelectListTheme as getSelectListTheme11 } from "@earendil-works/pi-coding-agent";
|
|
1756
|
+
import { visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
|
|
1757
|
+
var renderHeader = (header, width) => {
|
|
1758
|
+
const theme = getMarkdownTheme9();
|
|
1759
|
+
const selectTheme = getSelectListTheme11();
|
|
1760
|
+
const rule2 = header.focused === true ? selectTheme.selectedPrefix : theme.codeBlockBorder;
|
|
1761
|
+
const title = header.focused === true ? selectTheme.selectedText : theme.heading;
|
|
1762
|
+
const ruleCharacter = header.focused === true ? "\u2501" : "\u2500";
|
|
1763
|
+
const detail = header.detail === void 0 ? "" : ` ${selectTheme.description(header.detail)}`;
|
|
1764
|
+
const label = `${rule2(ruleCharacter.repeat(2))} ${title(header.title)}${detail} `;
|
|
1765
|
+
const ruleWidth = Math.max(0, width - visibleWidth3(label));
|
|
1766
|
+
return fitCampUiLine(`${label}${rule2(ruleCharacter.repeat(ruleWidth))}`, width);
|
|
1767
|
+
};
|
|
1768
|
+
var makeCampUiSection = (options) => {
|
|
1769
|
+
let childHeights = options.children.map(() => 0);
|
|
1770
|
+
const components = options.children.map((child) => child.component);
|
|
1771
|
+
const pointerComponents = components.some((component) => component.handlePointer !== void 0);
|
|
1772
|
+
const inputComponents = components.some((component) => component.handleInput !== void 0);
|
|
1773
|
+
const invalidatingComponents = components.some((component) => component.invalidate !== void 0);
|
|
1774
|
+
const disposableComponents = components.some((component) => component.dispose !== void 0);
|
|
1775
|
+
return {
|
|
1776
|
+
sectionSize: options.size,
|
|
1777
|
+
...components.some((component) => component.focusable === true) ? { focusable: true } : {},
|
|
1778
|
+
render: (viewport) => {
|
|
1779
|
+
if (viewport.height <= 0) {
|
|
1780
|
+
childHeights = options.children.map(() => 0);
|
|
1781
|
+
return [];
|
|
1782
|
+
}
|
|
1783
|
+
childHeights = [
|
|
1784
|
+
...allocateCampUiSectionRows(
|
|
1785
|
+
viewport.height - 1,
|
|
1786
|
+
options.children.map((child) => child.size)
|
|
1787
|
+
)
|
|
1788
|
+
];
|
|
1789
|
+
return [
|
|
1790
|
+
renderHeader(options.header(), viewport.width),
|
|
1791
|
+
...options.children.flatMap((child, index) => {
|
|
1792
|
+
const height = childHeights[index] ?? 0;
|
|
1793
|
+
const lines = child.component.render({ width: viewport.width, height }).slice(0, height);
|
|
1794
|
+
return [...lines, ...Array.from({ length: height - lines.length }, () => "")];
|
|
1795
|
+
})
|
|
1796
|
+
];
|
|
1797
|
+
},
|
|
1798
|
+
...inputComponents ? {
|
|
1799
|
+
handleInput: (input) => [...components].reverse().some((component) => component.handleInput?.(input) === true)
|
|
1800
|
+
} : {},
|
|
1801
|
+
...pointerComponents ? {
|
|
1802
|
+
handlePointer: (pointer) => {
|
|
1803
|
+
const bodyY = pointer.y - 1;
|
|
1804
|
+
if (bodyY < 0) return [];
|
|
1805
|
+
let offset = 0;
|
|
1806
|
+
for (const [index, child] of options.children.entries()) {
|
|
1807
|
+
const height = childHeights[index] ?? 0;
|
|
1808
|
+
if (bodyY >= offset && bodyY < offset + height) {
|
|
1809
|
+
return child.component.handlePointer?.({ ...pointer, y: bodyY - offset }) ?? [];
|
|
1810
|
+
}
|
|
1811
|
+
offset += height;
|
|
1812
|
+
}
|
|
1813
|
+
return [];
|
|
1814
|
+
}
|
|
1815
|
+
} : {},
|
|
1816
|
+
...invalidatingComponents ? {
|
|
1817
|
+
invalidate: () => {
|
|
1818
|
+
for (const component of components) component.invalidate?.();
|
|
1819
|
+
}
|
|
1820
|
+
} : {},
|
|
1821
|
+
...disposableComponents ? {
|
|
1822
|
+
dispose: Effect_exports.forEach(
|
|
1823
|
+
[...components].reverse(),
|
|
1824
|
+
(component) => component.dispose ?? Effect_exports.void,
|
|
1825
|
+
{ discard: true }
|
|
1826
|
+
)
|
|
1827
|
+
} : {}
|
|
1828
|
+
};
|
|
1829
|
+
};
|
|
1830
|
+
var decorateDefaultCampUiSections = (context, slots) => {
|
|
1831
|
+
const decorated = new Map(slots);
|
|
1832
|
+
const component = (slotId) => slots.get(slotId) ?? emptyCampUiComponent;
|
|
1833
|
+
decorated.set(
|
|
1834
|
+
CampUiSlot.campHeader,
|
|
1835
|
+
makeCampUiSection({
|
|
1836
|
+
header: () => ({ title: "Camp" }),
|
|
1837
|
+
size: defaultCampUiLeftSectionSizes.camp,
|
|
1838
|
+
children: [
|
|
1839
|
+
{
|
|
1840
|
+
component: component(CampUiSlot.campHeader),
|
|
1841
|
+
size: { kind: CampUiSectionSizeKind.fixed, rows: 2 }
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
component: component(CampUiSlot.seat),
|
|
1845
|
+
size: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 1 }
|
|
1846
|
+
}
|
|
1847
|
+
]
|
|
1848
|
+
})
|
|
1849
|
+
);
|
|
1850
|
+
decorated.delete(CampUiSlot.seat);
|
|
1851
|
+
decorated.set(
|
|
1852
|
+
CampUiSlot.resources,
|
|
1853
|
+
makeCampUiSection({
|
|
1854
|
+
header: () => ({ title: "Resources" }),
|
|
1855
|
+
size: defaultCampUiLeftSectionSizes.resources,
|
|
1856
|
+
children: [
|
|
1857
|
+
{
|
|
1858
|
+
component: component(CampUiSlot.resources),
|
|
1859
|
+
size: { kind: CampUiSectionSizeKind.grow, weight: 1 }
|
|
1860
|
+
}
|
|
1861
|
+
]
|
|
1862
|
+
})
|
|
1863
|
+
);
|
|
1864
|
+
decorated.set(
|
|
1865
|
+
CampUiSlot.roster,
|
|
1866
|
+
makeCampUiSection({
|
|
1867
|
+
header: () => ({
|
|
1868
|
+
title: "Workers",
|
|
1869
|
+
detail: String(context.readModel().snapshot.workers.length),
|
|
1870
|
+
focused: context.readModel().state.focusedSlotId === CampUiSlot.roster
|
|
1871
|
+
}),
|
|
1872
|
+
size: defaultCampUiLeftSectionSizes.workers,
|
|
1873
|
+
children: [
|
|
1874
|
+
{
|
|
1875
|
+
component: component(CampUiSlot.roster),
|
|
1876
|
+
size: { kind: CampUiSectionSizeKind.grow, weight: 1 }
|
|
1877
|
+
}
|
|
1878
|
+
]
|
|
1879
|
+
})
|
|
1880
|
+
);
|
|
1881
|
+
return decorated;
|
|
1882
|
+
};
|
|
1883
|
+
|
|
1884
|
+
// packages/host-ui/src/pi/mountPiCampUi.ts
|
|
1885
|
+
import { initTheme } from "@earendil-works/pi-coding-agent";
|
|
1886
|
+
import { ProcessTerminal, TUI } from "@earendil-works/pi-tui";
|
|
1887
|
+
|
|
1888
|
+
// packages/host-ui/src/pi/piCampUiRoot.ts
|
|
1889
|
+
import { getMarkdownTheme as getMarkdownTheme10 } from "@earendil-works/pi-coding-agent";
|
|
1890
|
+
import { Key as Key3, matchesKey as matchesKey3 } from "@earendil-works/pi-tui";
|
|
1891
|
+
|
|
1892
|
+
// packages/host-ui/src/pi/runtime/campUiPointerInput.ts
|
|
1893
|
+
var CampUiPointerAction = {
|
|
1894
|
+
press: "press",
|
|
1895
|
+
release: "release",
|
|
1896
|
+
wheel: "wheel"
|
|
1897
|
+
};
|
|
1898
|
+
var CampUiPointerButton = {
|
|
1899
|
+
left: "left",
|
|
1900
|
+
middle: "middle",
|
|
1901
|
+
right: "right"
|
|
1902
|
+
};
|
|
1903
|
+
var CampUiPointerDirection = {
|
|
1904
|
+
up: "up",
|
|
1905
|
+
down: "down"
|
|
1906
|
+
};
|
|
1907
|
+
var sgrPointerPattern = /^<(\d+);(\d+);(\d+)([Mm])$/u;
|
|
1908
|
+
var parseCampUiPointerInput = (input) => {
|
|
1909
|
+
if (!input.startsWith("\x1B[")) return void 0;
|
|
1910
|
+
const match = sgrPointerPattern.exec(input.slice(2));
|
|
1911
|
+
if (match === null) return void 0;
|
|
1912
|
+
const code = Number(match[1]);
|
|
1913
|
+
const column = Number(match[2]);
|
|
1914
|
+
const row = Number(match[3]);
|
|
1915
|
+
if (!Number.isSafeInteger(code) || column < 1 || row < 1) return void 0;
|
|
1916
|
+
const base = {
|
|
1917
|
+
x: column - 1,
|
|
1918
|
+
y: row - 1,
|
|
1919
|
+
shift: (code & 4) !== 0,
|
|
1920
|
+
alt: (code & 8) !== 0,
|
|
1921
|
+
control: (code & 16) !== 0
|
|
1922
|
+
};
|
|
1923
|
+
if ((code & 64) !== 0) {
|
|
1924
|
+
return {
|
|
1925
|
+
...base,
|
|
1926
|
+
action: CampUiPointerAction.wheel,
|
|
1927
|
+
direction: (code & 1) === 0 ? CampUiPointerDirection.up : CampUiPointerDirection.down
|
|
1928
|
+
};
|
|
1929
|
+
}
|
|
1930
|
+
const button = [CampUiPointerButton.left, CampUiPointerButton.middle, CampUiPointerButton.right][code & 3];
|
|
1931
|
+
if (button === void 0) return void 0;
|
|
1932
|
+
return {
|
|
1933
|
+
...base,
|
|
1934
|
+
action: match[4] === "M" ? CampUiPointerAction.press : CampUiPointerAction.release,
|
|
1935
|
+
button
|
|
1936
|
+
};
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
// packages/host-ui/src/pi/runtime/campUiPointerRouting.ts
|
|
1940
|
+
var routeCampUiPointer = (frame, pointer, slots) => {
|
|
1941
|
+
if (frame === void 0) return [];
|
|
1942
|
+
const hit = frame.regions.find(
|
|
1943
|
+
(region2) => pointer.x >= region2.x && pointer.x < region2.x + region2.width && pointer.y >= region2.y && pointer.y < region2.y + region2.height
|
|
1944
|
+
);
|
|
1945
|
+
if (hit === void 0) return [];
|
|
1946
|
+
return slots.get(hit.slotId)?.handlePointer?.({
|
|
1947
|
+
...pointer,
|
|
1948
|
+
x: pointer.x - hit.x,
|
|
1949
|
+
y: pointer.y - hit.y,
|
|
1950
|
+
mode: frame.mode
|
|
1951
|
+
}) ?? [];
|
|
1952
|
+
};
|
|
1953
|
+
|
|
1954
|
+
// packages/host-ui/src/pi/piCampUiRoot.ts
|
|
1955
|
+
var focusDirectionFromInput = (input) => {
|
|
1956
|
+
if (matchesKey3(input, Key3.left)) return CampUiFocusDirection.left;
|
|
1957
|
+
if (matchesKey3(input, Key3.right)) return CampUiFocusDirection.right;
|
|
1958
|
+
if (matchesKey3(input, Key3.up)) return CampUiFocusDirection.up;
|
|
1959
|
+
return matchesKey3(input, Key3.down) ? CampUiFocusDirection.down : void 0;
|
|
1960
|
+
};
|
|
1961
|
+
var focusableSlotsInFrame = (frame, slots) => [
|
|
1962
|
+
...new Set(
|
|
1963
|
+
(frame?.regions ?? []).filter((region2) => slots.get(region2.slotId)?.focusable === true).map((region2) => region2.slotId)
|
|
1964
|
+
)
|
|
1965
|
+
];
|
|
1966
|
+
var PiCampUiRoot = class {
|
|
1967
|
+
mode = CampUiLayoutMode.narrow;
|
|
1968
|
+
frame;
|
|
1969
|
+
context;
|
|
1970
|
+
slots;
|
|
1971
|
+
layout;
|
|
1972
|
+
getHeight;
|
|
1973
|
+
wheelDirectionFromInput;
|
|
1974
|
+
constructor(context, slots, layout, getHeight, wheelDirectionFromInput) {
|
|
1975
|
+
this.context = context;
|
|
1976
|
+
this.slots = slots;
|
|
1977
|
+
this.layout = layout;
|
|
1978
|
+
this.getHeight = getHeight;
|
|
1979
|
+
this.wheelDirectionFromInput = wheelDirectionFromInput;
|
|
1980
|
+
}
|
|
1981
|
+
render(width) {
|
|
1982
|
+
const height = Math.max(0, this.getHeight());
|
|
1983
|
+
const viewport = { width: Math.max(0, width - 1), height };
|
|
1984
|
+
const frame = this.layout.resolve(viewport, this.context.readModel().state, this.slots);
|
|
1985
|
+
this.frame = frame;
|
|
1986
|
+
this.mode = frame.mode;
|
|
1987
|
+
const state = this.context.readModel().state;
|
|
1988
|
+
const focusOrder = focusableSlotsInFrame(frame, this.slots);
|
|
1989
|
+
if (!focusOrder.includes(state.focusedSlotId)) {
|
|
1990
|
+
const fallback = focusOrder[0];
|
|
1991
|
+
if (fallback !== void 0) this.context.emit(CampUiIntents.FocusSlot({ slotId: fallback }));
|
|
1992
|
+
}
|
|
1993
|
+
const detailVisible = frame.regions.some((region2) => region2.slotId === CampUiSlot.transcript);
|
|
1994
|
+
if (this.context.readModel().state.detailVisible !== detailVisible) {
|
|
1995
|
+
this.context.emit(CampUiIntents.SetTranscriptVisible({ visible: detailVisible }));
|
|
1996
|
+
}
|
|
1997
|
+
const rendered = new Map(
|
|
1998
|
+
frame.regions.map((region2) => [
|
|
1999
|
+
region2,
|
|
2000
|
+
this.slots.get(region2.slotId)?.render({ width: region2.width, height: region2.height }) ?? []
|
|
2001
|
+
])
|
|
2002
|
+
);
|
|
2003
|
+
return Array.from({ length: height }, (_, row) => {
|
|
2004
|
+
const segments = [];
|
|
2005
|
+
for (const [region2, lines] of rendered) {
|
|
2006
|
+
if (row < region2.y || row >= region2.y + region2.height) continue;
|
|
2007
|
+
segments.push({
|
|
2008
|
+
x: region2.x,
|
|
2009
|
+
width: region2.width,
|
|
2010
|
+
text: lines[row - region2.y] ?? ""
|
|
2011
|
+
});
|
|
2012
|
+
}
|
|
2013
|
+
for (const divider of frame.dividers) {
|
|
2014
|
+
if (row >= divider.y && row < divider.y + divider.height) {
|
|
2015
|
+
segments.push({
|
|
2016
|
+
x: divider.x,
|
|
2017
|
+
width: 1,
|
|
2018
|
+
text: getMarkdownTheme10().codeBlockBorder("\u2502")
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
segments.sort((left, right) => left.x - right.x);
|
|
2023
|
+
let cursor = 0;
|
|
2024
|
+
let line = "";
|
|
2025
|
+
for (const segment of segments) {
|
|
2026
|
+
line += " ".repeat(Math.max(0, segment.x - cursor));
|
|
2027
|
+
line += fitCampUiLine(segment.text, segment.width);
|
|
2028
|
+
cursor = segment.x + segment.width;
|
|
2029
|
+
}
|
|
2030
|
+
return fitCampUiLine(
|
|
2031
|
+
`${line}${" ".repeat(Math.max(0, viewport.width - cursor))}`,
|
|
2032
|
+
viewport.width
|
|
2033
|
+
);
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
handleInput(input) {
|
|
2037
|
+
const pointer = parseCampUiPointerInput(input);
|
|
2038
|
+
if (pointer !== void 0) {
|
|
2039
|
+
for (const intent of routeCampUiPointer(this.frame, pointer, this.slots)) {
|
|
2040
|
+
this.context.emit(intent);
|
|
2041
|
+
}
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
2044
|
+
const wheelDirection = this.wheelDirectionFromInput?.(input);
|
|
2045
|
+
if (wheelDirection !== void 0) {
|
|
2046
|
+
const model2 = this.context.readModel();
|
|
2047
|
+
const workerId2 = model2.state.selectedWorkerId;
|
|
2048
|
+
const transcriptVisible = this.frame?.regions.some((region2) => region2.slotId === CampUiSlot.transcript) === true;
|
|
2049
|
+
if (workerId2 !== void 0 && transcriptVisible) {
|
|
2050
|
+
this.context.emit(
|
|
2051
|
+
CampUiIntents.ScrollWorker({
|
|
2052
|
+
workerId: workerId2,
|
|
2053
|
+
delta: wheelDirection === "up" ? 3 : -3
|
|
2054
|
+
})
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
const model = this.context.readModel();
|
|
2060
|
+
const focusOrder = focusableSlotsInFrame(this.frame, this.slots);
|
|
2061
|
+
const focusedSlotId = focusOrder.includes(model.state.focusedSlotId) ? model.state.focusedSlotId : focusOrder[0] ?? model.state.focusedSlotId;
|
|
2062
|
+
if (focusedSlotId !== model.state.focusedSlotId) {
|
|
2063
|
+
this.context.emit(CampUiIntents.FocusSlot({ slotId: focusedSlotId }));
|
|
2064
|
+
}
|
|
2065
|
+
if (this.slots.get(focusedSlotId)?.handleInput?.(input) === true) return;
|
|
2066
|
+
if (matchesKey3(input, Key3.escape)) {
|
|
2067
|
+
if (focusedSlotId === CampUiSlot.composer) {
|
|
2068
|
+
this.context.emit(CampUiIntents.FocusSlot({ slotId: CampUiSlot.transcript }));
|
|
2069
|
+
} else if (model.state.screen === CampUiScreen.worker) {
|
|
2070
|
+
this.context.emit(CampUiIntents.ActivateScreen({ screen: CampUiScreen.camp }));
|
|
2071
|
+
this.context.emit(CampUiIntents.FocusDetailBlock({}));
|
|
2072
|
+
this.context.emit(CampUiIntents.FocusSlot({ slotId: CampUiSlot.roster }));
|
|
2073
|
+
}
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
if (matchesKey3(input, Key3.tab) || matchesKey3(input, "shift+tab")) {
|
|
2077
|
+
if (focusOrder.length === 0) return;
|
|
2078
|
+
const current = focusOrder.indexOf(focusedSlotId);
|
|
2079
|
+
const delta = matchesKey3(input, "shift+tab") ? -1 : 1;
|
|
2080
|
+
const base = current < 0 ? delta > 0 ? -1 : 0 : current;
|
|
2081
|
+
const next = focusOrder[(base + delta + focusOrder.length) % focusOrder.length];
|
|
2082
|
+
if (next === void 0) return;
|
|
2083
|
+
this.context.emit(CampUiIntents.FocusSlot({ slotId: next }));
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
if (focusedSlotId !== CampUiSlot.composer && input === "n") {
|
|
2087
|
+
this.context.emit(CampUiIntents.NextPendingAsk());
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
if (focusedSlotId === CampUiSlot.transcript && model.state.focusedDetailBlockId !== void 0 && matchesKey3(input, Key3.enter)) {
|
|
2091
|
+
this.context.emit(
|
|
2092
|
+
CampUiIntents.ToggleDetailBlock({ blockId: model.state.focusedDetailBlockId })
|
|
2093
|
+
);
|
|
2094
|
+
return;
|
|
2095
|
+
}
|
|
2096
|
+
if (focusedSlotId === CampUiSlot.roster) {
|
|
2097
|
+
const delta = matchesKey3(input, Key3.up) ? -1 : matchesKey3(input, Key3.down) ? 1 : void 0;
|
|
2098
|
+
if (delta !== void 0) {
|
|
2099
|
+
const current = model.snapshot.workers.findIndex(
|
|
2100
|
+
(worker) => worker.snapshot.worker.id === model.state.selectedWorkerId
|
|
2101
|
+
);
|
|
2102
|
+
const next = current < 0 ? 0 : current + delta;
|
|
2103
|
+
if (next >= 0 && next < model.snapshot.workers.length && next !== current) {
|
|
2104
|
+
this.context.emit(CampUiIntents.SelectRelativeWorker({ delta }));
|
|
2105
|
+
return;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
if (matchesKey3(input, Key3.enter) && model.state.selectedWorkerId !== void 0) {
|
|
2109
|
+
this.context.emit(CampUiIntents.OpenWorker({ workerId: model.state.selectedWorkerId }));
|
|
2110
|
+
this.context.emit(
|
|
2111
|
+
CampUiIntents.FocusSlot({
|
|
2112
|
+
slotId: this.mode === CampUiLayoutMode.wide ? CampUiSlot.composer : CampUiSlot.transcript
|
|
2113
|
+
})
|
|
2114
|
+
);
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
const workerId = model.state.selectedWorkerId;
|
|
2119
|
+
if (focusedSlotId === CampUiSlot.transcript && workerId !== void 0 && matchesKey3(input, Key3.end)) {
|
|
2120
|
+
this.context.emit(CampUiIntents.JumpToWorkerTail({ workerId }));
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (focusedSlotId === CampUiSlot.transcript && workerId !== void 0 && (matchesKey3(input, Key3.pageUp) || matchesKey3(input, Key3.pageDown))) {
|
|
2124
|
+
this.context.emit(
|
|
2125
|
+
CampUiIntents.ScrollWorker({
|
|
2126
|
+
workerId,
|
|
2127
|
+
delta: (matchesKey3(input, Key3.pageUp) ? 1 : -1) * Math.max(
|
|
2128
|
+
1,
|
|
2129
|
+
(this.frame?.regions.find((region2) => region2.slotId === CampUiSlot.transcript)?.height ?? 10) - 2
|
|
2130
|
+
)
|
|
2131
|
+
})
|
|
2132
|
+
);
|
|
2133
|
+
return;
|
|
2134
|
+
}
|
|
2135
|
+
const direction = focusDirectionFromInput(input);
|
|
2136
|
+
if (direction === void 0) return;
|
|
2137
|
+
const target = findCampUiSpatialFocusTarget(
|
|
2138
|
+
this.frame,
|
|
2139
|
+
focusedSlotId,
|
|
2140
|
+
new Set(focusOrder),
|
|
2141
|
+
direction
|
|
2142
|
+
);
|
|
2143
|
+
if (target !== void 0) this.context.emit(CampUiIntents.FocusSlot({ slotId: target }));
|
|
2144
|
+
}
|
|
2145
|
+
invalidate() {
|
|
2146
|
+
for (const component of this.slots.values()) component.invalidate?.();
|
|
2147
|
+
}
|
|
2148
|
+
};
|
|
2149
|
+
|
|
2150
|
+
// packages/host-ui/src/pi/runtime/campUiPresentationClock.ts
|
|
2151
|
+
var hasPendingCampUiAsk = (model) => model.snapshot.workers.some(
|
|
2152
|
+
(worker) => worker.asks.some((ask) => ask.state === CampAskState.pending)
|
|
2153
|
+
);
|
|
2154
|
+
var runCampUiPresentationClock = (readModel, controller) => Effect_exports.forever(
|
|
2155
|
+
Effect_exports.sleep("1 second").pipe(
|
|
2156
|
+
Effect_exports.zipRight(
|
|
2157
|
+
Effect_exports.suspend(
|
|
2158
|
+
() => hasPendingCampUiAsk(readModel()) ? Clock_exports.currentTimeMillis.pipe(Effect_exports.flatMap(controller.setPresentationNow)) : Effect_exports.void
|
|
2159
|
+
)
|
|
2160
|
+
)
|
|
2161
|
+
)
|
|
2162
|
+
);
|
|
2163
|
+
|
|
2164
|
+
// packages/host-ui/src/pi/runtime/campUiSubscriptionRetry.ts
|
|
2165
|
+
var CAMP_UI_SUBSCRIPTION_MAX_RETRIES = 4;
|
|
2166
|
+
var CAMP_UI_SUBSCRIPTION_BASE_DELAY_MS = 250;
|
|
2167
|
+
var campUiSubscriptionRetryDelay = (attempt) => CAMP_UI_SUBSCRIPTION_BASE_DELAY_MS * 2 ** Math.max(0, attempt - 1);
|
|
2168
|
+
var sameCampUiRuntimeHealth = (left, right) => left.status === right.status && left.operation === right.operation && left.message === right.message && left.attempt === right.attempt && left.nextRetryAt === right.nextRetryAt;
|
|
2169
|
+
|
|
2170
|
+
// packages/host-ui/src/pi/runtime/campUiTerminalModes.ts
|
|
2171
|
+
var ENTER_ALTERNATE_SCREEN = "\x1B[?1049h";
|
|
2172
|
+
var LEAVE_ALTERNATE_SCREEN = "\x1B[?1049l";
|
|
2173
|
+
var ENABLE_ALTERNATE_SCROLL = "\x1B[?1007h";
|
|
2174
|
+
var DISABLE_ALTERNATE_SCROLL = "\x1B[?1007l";
|
|
2175
|
+
var CampUiWheelDirection = {
|
|
2176
|
+
down: "down",
|
|
2177
|
+
up: "up"
|
|
2178
|
+
};
|
|
2179
|
+
var makeCampUiTerminalModes = (terminal) => {
|
|
2180
|
+
let alternateScreenActive = false;
|
|
2181
|
+
let alternateScrollActive = false;
|
|
2182
|
+
return {
|
|
2183
|
+
enter: () => {
|
|
2184
|
+
if (alternateScreenActive) return;
|
|
2185
|
+
terminal.write(ENTER_ALTERNATE_SCREEN);
|
|
2186
|
+
alternateScreenActive = true;
|
|
2187
|
+
},
|
|
2188
|
+
exit: () => {
|
|
2189
|
+
if (alternateScrollActive) {
|
|
2190
|
+
terminal.write(DISABLE_ALTERNATE_SCROLL);
|
|
2191
|
+
alternateScrollActive = false;
|
|
2192
|
+
}
|
|
2193
|
+
if (alternateScreenActive) {
|
|
2194
|
+
terminal.write(LEAVE_ALTERNATE_SCREEN);
|
|
2195
|
+
alternateScreenActive = false;
|
|
2196
|
+
}
|
|
2197
|
+
},
|
|
2198
|
+
tryEnableAlternateScroll: () => {
|
|
2199
|
+
if (alternateScrollActive) return true;
|
|
2200
|
+
if (!alternateScreenActive || !terminal.kittyProtocolActive) return false;
|
|
2201
|
+
terminal.write(ENABLE_ALTERNATE_SCROLL);
|
|
2202
|
+
alternateScrollActive = true;
|
|
2203
|
+
return true;
|
|
2204
|
+
},
|
|
2205
|
+
wheelDirection: (input) => {
|
|
2206
|
+
if (!alternateScrollActive) return void 0;
|
|
2207
|
+
if (input === "\x1B[A") return CampUiWheelDirection.up;
|
|
2208
|
+
if (input === "\x1B[B") return CampUiWheelDirection.down;
|
|
2209
|
+
return void 0;
|
|
2210
|
+
}
|
|
2211
|
+
};
|
|
2212
|
+
};
|
|
2213
|
+
|
|
2214
|
+
// packages/host-ui/src/pi/mountPiCampUi.ts
|
|
2215
|
+
var mountPiCampUi = (options) => Effect_exports.gen(function* () {
|
|
2216
|
+
const startedAt = yield* Clock_exports.currentTimeMillis;
|
|
2217
|
+
const healthRef = yield* SubscriptionRef_exports.make({
|
|
2218
|
+
status: CampUiRuntimeStatus.starting,
|
|
2219
|
+
operation: "ui.mount",
|
|
2220
|
+
updatedAt: startedAt
|
|
2221
|
+
});
|
|
2222
|
+
const setHealth = (health) => SubscriptionRef_exports.get(healthRef).pipe(
|
|
2223
|
+
Effect_exports.flatMap(
|
|
2224
|
+
(current2) => sameCampUiRuntimeHealth(current2, { ...health, updatedAt: current2.updatedAt }) ? Effect_exports.void : Clock_exports.currentTimeMillis.pipe(
|
|
2225
|
+
Effect_exports.flatMap(
|
|
2226
|
+
(updatedAt) => SubscriptionRef_exports.set(healthRef, { ...health, updatedAt })
|
|
2227
|
+
)
|
|
2228
|
+
)
|
|
2229
|
+
)
|
|
2230
|
+
);
|
|
2231
|
+
yield* Effect_exports.sync(() => initTheme(void 0, false));
|
|
2232
|
+
const initial = yield* options.backend.snapshot;
|
|
2233
|
+
const store = yield* makeCampUiStore(initial, startedAt);
|
|
2234
|
+
let current = yield* store.current;
|
|
2235
|
+
const terminal = options.terminal ?? new ProcessTerminal();
|
|
2236
|
+
const terminalModes = makeCampUiTerminalModes(terminal);
|
|
2237
|
+
yield* Effect_exports.acquireRelease(
|
|
2238
|
+
Effect_exports.sync(() => terminalModes.enter()),
|
|
2239
|
+
() => Effect_exports.sync(() => terminalModes.exit())
|
|
2240
|
+
);
|
|
2241
|
+
const tui = new TUI(terminal, true);
|
|
2242
|
+
const runEffect = yield* FiberSet_exports.makeRuntime();
|
|
2243
|
+
const dispatch = (effect) => {
|
|
2244
|
+
runEffect(
|
|
2245
|
+
effect.pipe(
|
|
2246
|
+
Effect_exports.catchAllCause((cause) => store.controller.setActionError(Cause_exports.pretty(cause)))
|
|
2247
|
+
)
|
|
2248
|
+
);
|
|
2249
|
+
};
|
|
2250
|
+
const router = makeCampUiIntentRouter({
|
|
2251
|
+
backend: options.backend,
|
|
2252
|
+
controller: store.controller,
|
|
2253
|
+
readModel: () => current,
|
|
2254
|
+
dispatch
|
|
2255
|
+
});
|
|
2256
|
+
const context = {
|
|
2257
|
+
readModel: () => current,
|
|
2258
|
+
emit: router.emit
|
|
2259
|
+
};
|
|
2260
|
+
const detailBlocks = yield* composeCampUiDetailBlocks(
|
|
2261
|
+
defaultCampUiDetailBlockRenderers,
|
|
2262
|
+
options.detailBlocks ?? []
|
|
2263
|
+
);
|
|
2264
|
+
const composedSlots = yield* composeCampUiSlots(
|
|
2265
|
+
context,
|
|
2266
|
+
makeDefaultCampUiContributions(tui, detailBlocks),
|
|
2267
|
+
options.contributions ?? []
|
|
2268
|
+
);
|
|
2269
|
+
const slots = decorateDefaultCampUiSections(context, composedSlots);
|
|
2270
|
+
const root = new PiCampUiRoot(
|
|
2271
|
+
context,
|
|
2272
|
+
slots,
|
|
2273
|
+
options.layout ?? makeDefaultCampUiLayout(),
|
|
2274
|
+
() => terminal.rows,
|
|
2275
|
+
terminalModes.wheelDirection
|
|
2276
|
+
);
|
|
2277
|
+
tui.addChild(root);
|
|
2278
|
+
tui.setFocus(root);
|
|
2279
|
+
yield* Effect_exports.sync(() => {
|
|
2280
|
+
terminal.setTitle(initial.displayName);
|
|
2281
|
+
terminal.clearScreen();
|
|
2282
|
+
tui.start();
|
|
2283
|
+
});
|
|
2284
|
+
yield* Effect_exports.addFinalizer(
|
|
2285
|
+
() => Effect_exports.sync(() => {
|
|
2286
|
+
tui.stop();
|
|
2287
|
+
terminal.setProgress(false);
|
|
2288
|
+
}).pipe(
|
|
2289
|
+
Effect_exports.zipRight(setHealth({ status: CampUiRuntimeStatus.stopped, operation: "ui.stop" }))
|
|
2290
|
+
)
|
|
2291
|
+
);
|
|
2292
|
+
yield* Effect_exports.forkScoped(
|
|
2293
|
+
Effect_exports.gen(function* () {
|
|
2294
|
+
for (let attempt = 0; attempt < 25; attempt += 1) {
|
|
2295
|
+
if (yield* Effect_exports.sync(() => terminalModes.tryEnableAlternateScroll())) return;
|
|
2296
|
+
yield* Effect_exports.sleep("10 millis");
|
|
2297
|
+
}
|
|
2298
|
+
})
|
|
2299
|
+
);
|
|
2300
|
+
const fibers = yield* FiberSet_exports.make();
|
|
2301
|
+
yield* FiberSet_exports.run(
|
|
2302
|
+
fibers,
|
|
2303
|
+
store.changes.pipe(
|
|
2304
|
+
Stream_exports.runForEach(
|
|
2305
|
+
(model) => Effect_exports.sync(() => {
|
|
2306
|
+
current = model;
|
|
2307
|
+
tui.requestRender();
|
|
2308
|
+
})
|
|
2309
|
+
)
|
|
2310
|
+
)
|
|
2311
|
+
);
|
|
2312
|
+
yield* FiberSet_exports.run(
|
|
2313
|
+
fibers,
|
|
2314
|
+
runCampUiPresentationClock(() => current, store.controller)
|
|
2315
|
+
);
|
|
2316
|
+
yield* setHealth({ status: CampUiRuntimeStatus.ready, operation: "ui.mount" });
|
|
2317
|
+
const runSubscription = (failures) => options.backend.changes.pipe(
|
|
2318
|
+
Stream_exports.runForEach(
|
|
2319
|
+
(snapshot) => store.updateSnapshot(snapshot).pipe(
|
|
2320
|
+
Effect_exports.zipRight(
|
|
2321
|
+
setHealth({ status: CampUiRuntimeStatus.ready, operation: "ui.subscribe" })
|
|
2322
|
+
),
|
|
2323
|
+
Effect_exports.zipRight(store.controller.setActionError(void 0))
|
|
2324
|
+
)
|
|
2325
|
+
),
|
|
2326
|
+
Effect_exports.zipRight(
|
|
2327
|
+
Effect_exports.fail(
|
|
2328
|
+
new Error("Camp UI backend subscription ended before the UI scope was closed")
|
|
2329
|
+
)
|
|
2330
|
+
),
|
|
2331
|
+
Effect_exports.catchAll((cause) => {
|
|
2332
|
+
const attempt = failures + 1;
|
|
2333
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
2334
|
+
if (attempt > CAMP_UI_SUBSCRIPTION_MAX_RETRIES) {
|
|
2335
|
+
return setHealth({
|
|
2336
|
+
status: CampUiRuntimeStatus.failed,
|
|
2337
|
+
operation: "ui.subscribe",
|
|
2338
|
+
message,
|
|
2339
|
+
attempt
|
|
2340
|
+
}).pipe(Effect_exports.zipRight(store.controller.setActionError(message)));
|
|
2341
|
+
}
|
|
2342
|
+
const delay = campUiSubscriptionRetryDelay(attempt);
|
|
2343
|
+
return Clock_exports.currentTimeMillis.pipe(
|
|
2344
|
+
Effect_exports.flatMap(
|
|
2345
|
+
(now) => setHealth({
|
|
2346
|
+
status: CampUiRuntimeStatus.retrying,
|
|
2347
|
+
operation: "ui.subscribe",
|
|
2348
|
+
message,
|
|
2349
|
+
attempt,
|
|
2350
|
+
nextRetryAt: now + delay
|
|
2351
|
+
})
|
|
2352
|
+
),
|
|
2353
|
+
Effect_exports.zipRight(store.controller.setActionError(message)),
|
|
2354
|
+
Effect_exports.zipRight(Effect_exports.sleep(delay)),
|
|
2355
|
+
Effect_exports.zipRight(Effect_exports.suspend(() => runSubscription(attempt)))
|
|
2356
|
+
);
|
|
2357
|
+
})
|
|
2358
|
+
);
|
|
2359
|
+
yield* FiberSet_exports.run(fibers, runSubscription(0));
|
|
2360
|
+
tui.requestRender(true);
|
|
2361
|
+
return {
|
|
2362
|
+
controller: store.controller,
|
|
2363
|
+
tui,
|
|
2364
|
+
health: { current: SubscriptionRef_exports.get(healthRef), changes: healthRef.changes }
|
|
2365
|
+
};
|
|
2366
|
+
});
|
|
2367
|
+
|
|
2368
|
+
// packages/host-ui/src/run-selector/campRunSelector.ts
|
|
2369
|
+
import { getMarkdownTheme as getMarkdownTheme11, getSelectListTheme as getSelectListTheme12, initTheme as initTheme2 } from "@earendil-works/pi-coding-agent";
|
|
2370
|
+
import {
|
|
2371
|
+
Key as Key4,
|
|
2372
|
+
matchesKey as matchesKey4,
|
|
2373
|
+
ProcessTerminal as ProcessTerminal2,
|
|
2374
|
+
TUI as TUI2
|
|
2375
|
+
} from "@earendil-works/pi-tui";
|
|
2376
|
+
var PiCampRunSelectorRoot = class {
|
|
2377
|
+
selectedIndex = 0;
|
|
2378
|
+
items;
|
|
2379
|
+
getHeight;
|
|
2380
|
+
select;
|
|
2381
|
+
requestRender;
|
|
2382
|
+
constructor(items, getHeight, select, requestRender) {
|
|
2383
|
+
this.items = items;
|
|
2384
|
+
this.getHeight = getHeight;
|
|
2385
|
+
this.select = select;
|
|
2386
|
+
this.requestRender = requestRender;
|
|
2387
|
+
}
|
|
2388
|
+
render(width) {
|
|
2389
|
+
const height = Math.max(0, this.getHeight());
|
|
2390
|
+
const viewportWidth = Math.max(0, width - 1);
|
|
2391
|
+
const markdown = getMarkdownTheme11();
|
|
2392
|
+
const selection = getSelectListTheme12();
|
|
2393
|
+
const header = `${markdown.codeBlockBorder("\u2500\u2500")} ${markdown.heading("Resume camp")} ${markdown.codeBlockBorder("\u2500".repeat(Math.max(0, viewportWidth - 16)))}`;
|
|
2394
|
+
const available = Math.max(0, height - 2);
|
|
2395
|
+
const capacity = Math.max(1, Math.floor(available / 2));
|
|
2396
|
+
const start = Math.min(
|
|
2397
|
+
Math.max(0, this.selectedIndex - Math.floor(capacity / 2)),
|
|
2398
|
+
Math.max(0, this.items.length - capacity)
|
|
2399
|
+
);
|
|
2400
|
+
const visibleItems = this.items.slice(start, start + capacity);
|
|
2401
|
+
const rows2 = visibleItems.flatMap((item, index) => {
|
|
2402
|
+
const selected = start + index === this.selectedIndex;
|
|
2403
|
+
const prefix = selected ? selection.selectedPrefix("\u25CF ") : " ";
|
|
2404
|
+
const title = selected ? selection.selectedText(item.runId) : item.runId;
|
|
2405
|
+
const details = selection.description(
|
|
2406
|
+
`${item.lifecycle} \xB7 ${String(item.workerCount)} workers \xB7 ${item.updatedAt}`
|
|
2407
|
+
);
|
|
2408
|
+
return [
|
|
2409
|
+
fitCampUiLine(`${prefix}${title}`, viewportWidth),
|
|
2410
|
+
fitCampUiLine(` ${details}`, viewportWidth)
|
|
2411
|
+
];
|
|
2412
|
+
});
|
|
2413
|
+
const content = [fitCampUiLine(header, viewportWidth), "", ...rows2].slice(0, height);
|
|
2414
|
+
return [...content, ...Array.from({ length: Math.max(0, height - content.length) }, () => "")];
|
|
2415
|
+
}
|
|
2416
|
+
handleInput(input) {
|
|
2417
|
+
if (matchesKey4(input, Key4.escape) || input === "q") {
|
|
2418
|
+
this.select(void 0);
|
|
2419
|
+
return;
|
|
2420
|
+
}
|
|
2421
|
+
if (matchesKey4(input, Key4.enter)) {
|
|
2422
|
+
this.select(this.items[this.selectedIndex]?.runId);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
const delta = matchesKey4(input, Key4.up) ? -1 : matchesKey4(input, Key4.down) ? 1 : 0;
|
|
2426
|
+
if (delta === 0 || this.items.length === 0) return;
|
|
2427
|
+
this.selectedIndex = (this.selectedIndex + delta + this.items.length) % this.items.length;
|
|
2428
|
+
this.requestRender();
|
|
2429
|
+
}
|
|
2430
|
+
invalidate() {
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
var selectCampRun = (options) => Effect_exports.scoped(
|
|
2434
|
+
Effect_exports.gen(function* () {
|
|
2435
|
+
yield* Effect_exports.sync(() => initTheme2(void 0, false));
|
|
2436
|
+
const selected = yield* Deferred_exports.make();
|
|
2437
|
+
const terminal = options.terminal ?? new ProcessTerminal2();
|
|
2438
|
+
const modes = makeCampUiTerminalModes(terminal);
|
|
2439
|
+
yield* Effect_exports.acquireRelease(
|
|
2440
|
+
Effect_exports.sync(() => modes.enter()),
|
|
2441
|
+
() => Effect_exports.sync(() => modes.exit())
|
|
2442
|
+
);
|
|
2443
|
+
const tui = new TUI2(terminal, true);
|
|
2444
|
+
const root = new PiCampRunSelectorRoot(
|
|
2445
|
+
options.items,
|
|
2446
|
+
() => terminal.rows,
|
|
2447
|
+
(runId) => {
|
|
2448
|
+
Effect_exports.runFork(Deferred_exports.succeed(selected, runId));
|
|
2449
|
+
},
|
|
2450
|
+
() => tui.requestRender()
|
|
2451
|
+
);
|
|
2452
|
+
tui.addChild(root);
|
|
2453
|
+
tui.setFocus(root);
|
|
2454
|
+
yield* Effect_exports.acquireRelease(
|
|
2455
|
+
Effect_exports.sync(() => {
|
|
2456
|
+
terminal.setTitle("Resume camp");
|
|
2457
|
+
terminal.clearScreen();
|
|
2458
|
+
tui.start();
|
|
2459
|
+
tui.requestRender(true);
|
|
2460
|
+
}),
|
|
2461
|
+
() => Effect_exports.sync(() => tui.stop())
|
|
2462
|
+
);
|
|
2463
|
+
return yield* Deferred_exports.await(selected);
|
|
2464
|
+
})
|
|
2465
|
+
);
|
|
2466
|
+
|
|
2467
|
+
// packages/host-ui/src/index.ts
|
|
2468
|
+
var PACKAGE = "@priiisk/host-ui";
|
|
2469
|
+
|
|
2470
|
+
export {
|
|
2471
|
+
CampUiBackendError,
|
|
2472
|
+
CampUiBackend,
|
|
2473
|
+
CampUiPlacement,
|
|
2474
|
+
CampUiHealthStatus,
|
|
2475
|
+
CampUiScreen,
|
|
2476
|
+
campUiAskBlockId,
|
|
2477
|
+
makeInitialCampUiState,
|
|
2478
|
+
makeCampUiModel,
|
|
2479
|
+
findCampUiWorker,
|
|
2480
|
+
CampUiControllerError,
|
|
2481
|
+
makeCampUiStore,
|
|
2482
|
+
makeCampUiComponentGroup,
|
|
2483
|
+
emptyCampUiComponent,
|
|
2484
|
+
CampUiSlot,
|
|
2485
|
+
CampUiContributionStrategy,
|
|
2486
|
+
CampUiCompositionError,
|
|
2487
|
+
composeCampUiSlots,
|
|
2488
|
+
composeCampUiDetailBlocks,
|
|
2489
|
+
renderCampUiDetailBlocks,
|
|
2490
|
+
renderCampUiDetailBlockLayout,
|
|
2491
|
+
campUiPendingAskTargets,
|
|
2492
|
+
nextCampUiPendingAskTarget,
|
|
2493
|
+
CampUiIntents,
|
|
2494
|
+
makeCampUiIntentHandler,
|
|
2495
|
+
makeCampUiIntentRouter,
|
|
2496
|
+
CampUiFocusDirection,
|
|
2497
|
+
findCampUiSpatialFocusTarget,
|
|
2498
|
+
defaultCampUiLayoutPolicy,
|
|
2499
|
+
CampUiSectionSizeKind,
|
|
2500
|
+
allocateCampUiSectionRows,
|
|
2501
|
+
defaultCampUiLeftSectionSizes,
|
|
2502
|
+
CampUiLayoutMode,
|
|
2503
|
+
makeDefaultCampUiLayout,
|
|
2504
|
+
CampUiRuntimeStatus,
|
|
2505
|
+
hasCampUiTextContent,
|
|
2506
|
+
fitCampUiLine,
|
|
2507
|
+
fitCampUiLines,
|
|
2508
|
+
joinCampUiColumns,
|
|
2509
|
+
jsonText,
|
|
2510
|
+
compactNumber,
|
|
2511
|
+
CampUiTone,
|
|
2512
|
+
CampUiGlyph,
|
|
2513
|
+
styleCampUiTone,
|
|
2514
|
+
uniqueCampUiNames,
|
|
2515
|
+
renderCampUiStatus,
|
|
2516
|
+
renderCampUiState,
|
|
2517
|
+
renderCampUiWorkerState,
|
|
2518
|
+
renderSelectedCampUiRow,
|
|
2519
|
+
DEFAULT_DIRECTED_BLOCK_BODY_LINES,
|
|
2520
|
+
formatCampUiDuration,
|
|
2521
|
+
renderCampUiDirectedBlock,
|
|
2522
|
+
campUiPendingAskBlockRenderer,
|
|
2523
|
+
CampUiAssistantMessage,
|
|
2524
|
+
makeCampUiComposerComponent,
|
|
2525
|
+
campUiEquipmentBlockRenderer,
|
|
2526
|
+
renderCampUiPanel,
|
|
2527
|
+
CAMP_UI_DEFAULT_TOOL_PREVIEW_LINES,
|
|
2528
|
+
CAMP_UI_BASH_PREVIEW_LINES,
|
|
2529
|
+
renderCampUiToolResult,
|
|
2530
|
+
campUiTranscriptBlockRenderer,
|
|
2531
|
+
defaultCampUiDetailBlockRenderers,
|
|
2532
|
+
makeCampUiDetailComponent,
|
|
2533
|
+
makeDefaultCampUiContributions,
|
|
2534
|
+
makeCampUiSection,
|
|
2535
|
+
decorateDefaultCampUiSections,
|
|
2536
|
+
PiCampUiRoot,
|
|
2537
|
+
hasPendingCampUiAsk,
|
|
2538
|
+
runCampUiPresentationClock,
|
|
2539
|
+
CAMP_UI_SUBSCRIPTION_MAX_RETRIES,
|
|
2540
|
+
CAMP_UI_SUBSCRIPTION_BASE_DELAY_MS,
|
|
2541
|
+
campUiSubscriptionRetryDelay,
|
|
2542
|
+
sameCampUiRuntimeHealth,
|
|
2543
|
+
mountPiCampUi,
|
|
2544
|
+
PiCampRunSelectorRoot,
|
|
2545
|
+
selectCampRun,
|
|
2546
|
+
PACKAGE
|
|
2547
|
+
};
|