priiisk 0.1.20 → 0.1.21

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.
@@ -2,15 +2,16 @@ import { createRequire as __priiiskCreateRequire } from "node:module";
2
2
  const require = __priiiskCreateRequire(import.meta.url);
3
3
  import {
4
4
  CampAskState,
5
- get2 as get,
6
- make2 as make3,
5
+ CampWorkerState,
6
+ get2,
7
+ make2 as make4,
7
8
  modify,
8
9
  modifyEffect,
9
- set,
10
+ set as set2,
10
11
  tagsExhaustive,
11
12
  type,
12
- update
13
- } from "./chunk-S4NILFPX.js";
13
+ update as update2
14
+ } from "./chunk-U2FODXMR.js";
14
15
  import {
15
16
  Tag,
16
17
  TaggedError,
@@ -18,36 +19,44 @@ import {
18
19
  _void,
19
20
  acquireRelease,
20
21
  addFinalizer2 as addFinalizer,
21
- as2 as as,
22
+ as,
22
23
  catchAll,
23
- catchAllCause2 as catchAllCause,
24
+ catchAllCause,
24
25
  currentTimeMillis,
26
+ equipmentDescriptionLines,
25
27
  fail4 as fail,
26
- flatMap4 as flatMap,
28
+ flatMap3 as flatMap,
27
29
  forEach,
28
- forever2 as forever,
30
+ forever,
29
31
  forkScoped,
30
32
  gen,
33
+ get4 as get,
34
+ getAndSet,
31
35
  ignore,
32
36
  isInterruptedOnly,
33
37
  isSuccess,
34
- make12 as make2,
35
- make7 as make,
38
+ make14 as make3,
39
+ make6 as make,
40
+ make7 as make2,
36
41
  makeRuntime,
42
+ millis,
43
+ never,
37
44
  pretty,
38
45
  run2 as run,
39
46
  runForEach,
40
47
  runFork,
41
48
  scoped,
49
+ set2 as set,
42
50
  sleep,
43
- succeed3 as succeed,
44
- succeed4 as succeed2,
45
- suspend2 as suspend,
46
- sync2 as sync,
51
+ succeed2 as succeed,
52
+ succeed3 as succeed2,
53
+ suspend,
54
+ sync,
47
55
  taggedEnum,
48
- tap2 as tap,
49
- zipRight2 as zipRight
50
- } from "./chunk-26WAMIP2.js";
56
+ tap,
57
+ update,
58
+ zipRight
59
+ } from "./chunk-DPNPQG2D.js";
51
60
  import "./chunk-7UPFIIZD.js";
52
61
 
53
62
  // packages/host-ui/src/backend/campUiBackend.ts
@@ -76,6 +85,7 @@ var makeInitialCampUiState = (snapshot, presentationNow = 0) => ({
76
85
  screen: CampUiScreen.camp,
77
86
  focusedSlotId: "roster",
78
87
  detailVisible: false,
88
+ inventoryVisible: false,
79
89
  presentationNow,
80
90
  drafts: /* @__PURE__ */ new Map(),
81
91
  scrollOffsets: /* @__PURE__ */ new Map(),
@@ -139,8 +149,8 @@ var updateCampUiStringSet = (current, value, included) => {
139
149
  var CampUiControllerError = class extends TaggedError("CampUiControllerError") {
140
150
  };
141
151
  var makeCampUiStore = (initial, presentationNow = 0) => gen(function* () {
142
- const modelRef = yield* make3(makeCampUiModel(initial, presentationNow));
143
- const updateState = (mutate) => update(modelRef, (model) => ({ ...model, state: mutate(model.state) }));
152
+ const modelRef = yield* make4(makeCampUiModel(initial, presentationNow));
153
+ const updateState = (mutate) => update2(modelRef, (model) => ({ ...model, state: mutate(model.state) }));
144
154
  const controller = {
145
155
  selectWorker: (workerId) => modifyEffect(
146
156
  modelRef,
@@ -201,6 +211,10 @@ var makeCampUiStore = (initial, presentationNow = 0) => gen(function* () {
201
211
  if (state.detailVisible === detailVisible) return state;
202
212
  return { ...state, detailVisible };
203
213
  }),
214
+ setInventoryVisible: (inventoryVisible) => updateState((state) => {
215
+ if (state.inventoryVisible === inventoryVisible) return state;
216
+ return { ...state, inventoryVisible };
217
+ }),
204
218
  setDetailBlockExpanded: (blockId, expanded) => updateState((state) => ({
205
219
  ...state,
206
220
  expandedDetailBlockIds: updateCampUiStringSet(
@@ -224,7 +238,7 @@ var makeCampUiStore = (initial, presentationNow = 0) => gen(function* () {
224
238
  })
225
239
  };
226
240
  return {
227
- current: get(modelRef),
241
+ current: get2(modelRef),
228
242
  changes: modelRef.changes,
229
243
  updateSnapshot: (snapshot) => modify(modelRef, (current) => {
230
244
  const stale = current.snapshot.generation === snapshot.generation && snapshot.revision <= current.snapshot.revision;
@@ -275,13 +289,14 @@ var emptyCampUiComponent = {
275
289
  // packages/host-ui/src/composition/campUiSlots.ts
276
290
  var CampUiSlot = {
277
291
  campHeader: "camp-header",
292
+ activity: "activity",
293
+ equipment: "equipment",
278
294
  resources: "resources",
279
295
  roster: "roster",
280
296
  sessionHeader: "session-header",
281
297
  transcript: "transcript",
282
298
  composer: "composer",
283
- campFooter: "camp-footer",
284
- sessionFooter: "session-footer",
299
+ footer: "footer",
285
300
  overlay: "overlay"
286
301
  };
287
302
  var CampUiContributionStrategy = {
@@ -446,6 +461,7 @@ var makeCampUiIntentHandler = (options) => type().pipe(
446
461
  }),
447
462
  SelectWorker: ({ workerId }) => options.controller.selectWorker(workerId),
448
463
  SetDraft: ({ workerId, draft }) => options.controller.setDraft(workerId, draft),
464
+ SetInventoryVisible: ({ visible }) => options.controller.setInventoryVisible(visible),
449
465
  SetScrollOffset: ({ workerId, offset }) => options.controller.setScrollOffset(workerId, offset),
450
466
  SetTranscriptVisible: ({ visible }) => options.controller.setDetailVisible(visible),
451
467
  Steer: ({ workerId, message }) => message.trim() === "" ? _void : options.backend.steer({ workerId, message }).pipe(
@@ -648,6 +664,10 @@ var defaultCampUiLeftSectionSizes = {
648
664
  resources: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 1 },
649
665
  workers: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 1 }
650
666
  };
667
+ var defaultCampUiRightSectionSizes = {
668
+ activity: { kind: CampUiSectionSizeKind.grow, weight: 1, minimumRows: 2 },
669
+ equipment: { kind: CampUiSectionSizeKind.fixed, rows: 7 }
670
+ };
651
671
 
652
672
  // packages/host-ui/src/layout/campUiLayout.ts
653
673
  var CampUiLayoutMode = {
@@ -662,10 +682,18 @@ var leftSectionHeights = (bodyHeight, slots) => {
662
682
  const size = (slotId, fallback) => slots?.get(slotId)?.sectionSize ?? fallback;
663
683
  return allocateCampUiSectionRows(bodyHeight, [
664
684
  size(CampUiSlot.campHeader, defaultCampUiLeftSectionSizes.camp),
665
- size(CampUiSlot.resources, defaultCampUiLeftSectionSizes.resources),
666
685
  size(CampUiSlot.roster, defaultCampUiLeftSectionSizes.workers)
667
686
  ]);
668
687
  };
688
+ var rightSectionHeights = (bodyHeight, width, slots) => {
689
+ const minimumActivityRows = Math.min(units2(bodyHeight), 2);
690
+ const fallback = defaultCampUiRightSectionSizes.equipment.rows;
691
+ const preferred = units2(
692
+ slots?.get(CampUiSlot.equipment)?.preferredRows?.(units2(width)) ?? fallback
693
+ );
694
+ const equipmentHeight = fixedRows(Math.max(0, bodyHeight - minimumActivityRows), preferred);
695
+ return [Math.max(0, bodyHeight - equipmentHeight), equipmentHeight];
696
+ };
669
697
  var composerHeight = (availableHeight, width, policy, slots) => {
670
698
  const maximum = units2(policy.composerMaximumHeight);
671
699
  const minimum = Math.min(maximum, units2(policy.composerMinimumHeight));
@@ -679,7 +707,7 @@ var makeNarrowFrame = (viewport, state, policy, slots) => {
679
707
  const bodyHeight = Math.max(0, viewport.height - footerHeight);
680
708
  const footerY = bodyHeight;
681
709
  const workerScreen = state.screen === CampUiScreen.worker && state.selectedWorkerId !== void 0;
682
- const [campHeight = 0, resourcesHeight = 0, rosterHeight = 0] = workerScreen ? [] : leftSectionHeights(bodyHeight, slots);
710
+ const [campHeight = 0, rosterHeight = 0] = workerScreen ? [] : leftSectionHeights(bodyHeight, slots);
683
711
  const sessionHeaderHeight = workerScreen ? fixedRows(bodyHeight, policy.sessionHeaderHeight) : 0;
684
712
  const inputHeight = workerScreen ? composerHeight(bodyHeight - sessionHeaderHeight, viewport.width, policy, slots) : 0;
685
713
  const transcriptHeight = workerScreen ? bodyHeight - sessionHeaderHeight - inputHeight : 0;
@@ -688,8 +716,7 @@ var makeNarrowFrame = (viewport, state, policy, slots) => {
688
716
  dividers: [],
689
717
  regions: compact([
690
718
  region(CampUiSlot.campHeader, 0, 0, viewport.width, campHeight),
691
- region(CampUiSlot.resources, 0, campHeight, viewport.width, resourcesHeight),
692
- region(CampUiSlot.roster, 0, campHeight + resourcesHeight, viewport.width, rosterHeight),
719
+ region(CampUiSlot.roster, 0, campHeight, viewport.width, rosterHeight),
693
720
  region(CampUiSlot.sessionHeader, 0, 0, viewport.width, sessionHeaderHeight),
694
721
  region(CampUiSlot.transcript, 0, sessionHeaderHeight, viewport.width, transcriptHeight),
695
722
  region(
@@ -699,13 +726,7 @@ var makeNarrowFrame = (viewport, state, policy, slots) => {
699
726
  viewport.width,
700
727
  inputHeight
701
728
  ),
702
- region(
703
- workerScreen ? CampUiSlot.sessionFooter : CampUiSlot.campFooter,
704
- 0,
705
- footerY,
706
- viewport.width,
707
- footerHeight
708
- )
729
+ region(CampUiSlot.footer, 0, footerY, viewport.width, footerHeight)
709
730
  ])
710
731
  };
711
732
  };
@@ -739,6 +760,11 @@ var makeWideFrame = (viewport, policy, slots) => {
739
760
  ]);
740
761
  const centerX2 = leftWidth + leftGap;
741
762
  const rightX = centerX2 + centerWidth + rightGap;
763
+ const [activityHeight = 0, equipmentHeight = 0] = rightSectionHeights(
764
+ bodyHeight,
765
+ rightWidth,
766
+ slots
767
+ );
742
768
  const sessionHeaderHeight = fixedRows(bodyHeight, policy.sessionHeaderHeight);
743
769
  const inputHeight = composerHeight(bodyHeight - sessionHeaderHeight, centerWidth, policy, slots);
744
770
  const transcriptHeight = bodyHeight - sessionHeaderHeight - inputHeight;
@@ -764,9 +790,13 @@ var makeWideFrame = (viewport, policy, slots) => {
764
790
  centerWidth,
765
791
  inputHeight
766
792
  ),
767
- region(CampUiSlot.resources, rightX, bodyY, rightWidth, bodyHeight),
768
- region(CampUiSlot.campFooter, 0, footerY, leftWidth, footerHeight),
769
- region(CampUiSlot.sessionFooter, centerX2, footerY, viewport.width - centerX2, footerHeight)
793
+ region(CampUiSlot.activity, rightX, bodyY, rightWidth, activityHeight),
794
+ region(CampUiSlot.equipment, rightX, bodyY + activityHeight, rightWidth, equipmentHeight),
795
+ /*
796
+ * One line across the whole width: two footers pinned to the corners split
797
+ * along the track boundary and repeated what the worker cards already say.
798
+ */
799
+ region(CampUiSlot.footer, 0, footerY, viewport.width, footerHeight)
770
800
  ])
771
801
  };
772
802
  };
@@ -1276,50 +1306,12 @@ var makeCampUiComposerComponent = (context, tui) => {
1276
1306
  import { getSelectListTheme as getSelectListTheme8 } from "@earendil-works/pi-coding-agent";
1277
1307
  import { Key as Key2, matchesKey as matchesKey2 } from "@earendil-works/pi-tui";
1278
1308
 
1279
- // packages/host-ui/src/pi/blocks/campUiEquipmentBlock.ts
1280
- import { getMarkdownTheme as getMarkdownTheme4, UserMessageComponent } from "@earendil-works/pi-coding-agent";
1281
- var escapeMarkdown = (value) => value.replaceAll(/([\\`*_[\]{}()#+.!|>])/gu, "\\$1");
1282
- var renderNames = (names) => names.length === 0 ? "none" : names.map(escapeMarkdown).join(", ");
1283
- var renderMcpServers = (equipment) => renderNames(
1284
- equipment.mcp.serverNames.map((serverName) => {
1285
- const toolNames = equipment.mcp.toolNamesByServer?.[serverName];
1286
- return toolNames === void 0 ? serverName : `${serverName} (${toolNames.join(", ")})`;
1287
- })
1288
- );
1289
- var equipmentMarkdown = (equipment) => {
1290
- const role = equipment.role ?? equipment.preset ?? "unknown";
1291
- const tools = [.../* @__PURE__ */ new Set([...equipment.builtinTools, ...equipment.customToolNames])].sort();
1292
- const lines = [
1293
- `**Equipment \xB7 ${escapeMarkdown(role)}**`,
1294
- "",
1295
- `- **Tools:** ${renderNames(tools)}`,
1296
- `- **MCP:** ${renderMcpServers(equipment)}`,
1297
- `- **Skills:** ${renderNames(equipment.skillNames)}`,
1298
- `- **Access:** ${equipment.readOnly ? "read-only" : "read/write"}`
1299
- ];
1300
- return lines.join("\n");
1301
- };
1302
- var campUiEquipmentBlockRenderer = {
1303
- id: "role-equipment",
1304
- order: -100,
1305
- render: ({ worker, width }) => [
1306
- {
1307
- id: "role-equipment",
1308
- lines: new UserMessageComponent(
1309
- equipmentMarkdown(worker.snapshot.worker.equipment),
1310
- getMarkdownTheme4(),
1311
- 0
1312
- ).render(width)
1313
- }
1314
- ]
1315
- };
1316
-
1317
1309
  // packages/host-ui/src/pi/blocks/campUiTranscriptBlocks.ts
1318
- import { getMarkdownTheme as getMarkdownTheme7, UserMessageComponent as UserMessageComponent2 } from "@earendil-works/pi-coding-agent";
1310
+ import { getMarkdownTheme as getMarkdownTheme6, UserMessageComponent } from "@earendil-works/pi-coding-agent";
1319
1311
  import { Markdown as Markdown4 } from "@earendil-works/pi-tui";
1320
1312
 
1321
1313
  // packages/host-ui/src/pi/shared/campUiPanel.ts
1322
- import { getMarkdownTheme as getMarkdownTheme5, getSelectListTheme as getSelectListTheme5 } from "@earendil-works/pi-coding-agent";
1314
+ import { getMarkdownTheme as getMarkdownTheme4, getSelectListTheme as getSelectListTheme5 } from "@earendil-works/pi-coding-agent";
1323
1315
  import { truncateToWidth as truncateToWidth3, visibleWidth as visibleWidth2 } from "@earendil-works/pi-tui";
1324
1316
  var rule = (start, label, width, borderColor, labelColor) => {
1325
1317
  const prefix = `${borderColor(`${start}\u2500 `)}${labelColor(label)}${borderColor(" ")}`;
@@ -1328,7 +1320,7 @@ var rule = (start, label, width, borderColor, labelColor) => {
1328
1320
  };
1329
1321
  var renderCampUiPanel = (label, body, width) => {
1330
1322
  if (width <= 0) return [];
1331
- const borderColor = getMarkdownTheme5().codeBlockBorder;
1323
+ const borderColor = getMarkdownTheme4().codeBlockBorder;
1332
1324
  const labelColor = getSelectListTheme5().selectedText;
1333
1325
  if (width === 1) {
1334
1326
  return [borderColor("\u2502"), ...body.map(() => borderColor("\u2502")), borderColor("\u2570")];
@@ -1468,7 +1460,7 @@ var renderCampUiExploredGroup = (reads, width, expanded) => {
1468
1460
 
1469
1461
  // packages/host-ui/src/pi/blocks/campUiToolResult.ts
1470
1462
  import {
1471
- getMarkdownTheme as getMarkdownTheme6,
1463
+ getMarkdownTheme as getMarkdownTheme5,
1472
1464
  getSelectListTheme as getSelectListTheme7,
1473
1465
  truncateToVisualLines
1474
1466
  } from "@earendil-works/pi-coding-agent";
@@ -1543,13 +1535,13 @@ var renderCampUiToolResult = (result, width, expanded) => {
1543
1535
  ]);
1544
1536
  }
1545
1537
  const content = toolResultContent(result, true);
1546
- const lines = content === "" ? [] : new Markdown3(content, 0, 0, getMarkdownTheme6()).render(boundedWidth);
1538
+ const lines = content === "" ? [] : new Markdown3(content, 0, 0, getMarkdownTheme5()).render(boundedWidth);
1547
1539
  return keep([
1548
1540
  ...lines,
1549
1541
  ...result.errorMessage === void 0 ? [] : [getSelectListTheme7().noMatch(`Error: ${result.errorMessage}`)],
1550
1542
  ...result.toolResultDetails === void 0 ? [] : [
1551
1543
  getSelectListTheme7().description("Details"),
1552
- ...new Markdown3(jsonText(result.toolResultDetails), 0, 0, getMarkdownTheme6()).render(
1544
+ ...new Markdown3(jsonText(result.toolResultDetails), 0, 0, getMarkdownTheme5()).render(
1553
1545
  boundedWidth
1554
1546
  )
1555
1547
  ],
@@ -1596,7 +1588,7 @@ var renderMessage = (message, width) => {
1596
1588
  ] : [];
1597
1589
  return [
1598
1590
  ...attribution,
1599
- ...new UserMessageComponent2(markdown2, getMarkdownTheme7(), 0).render(width)
1591
+ ...new UserMessageComponent(markdown2, getMarkdownTheme6(), 0).render(width)
1600
1592
  ];
1601
1593
  }
1602
1594
  if (message.role === "assistant") {
@@ -1610,9 +1602,9 @@ var renderMessage = (message, width) => {
1610
1602
  const suffix = message.errorMessage === void 0 ? "" : `
1611
1603
 
1612
1604
  Error: ${message.errorMessage}`;
1613
- return new Markdown4(`${prefix}${markdown}${suffix}`, 0, 0, getMarkdownTheme7()).render(width);
1605
+ return new Markdown4(`${prefix}${markdown}${suffix}`, 0, 0, getMarkdownTheme6()).render(width);
1614
1606
  };
1615
- var renderMarkdown = (markdown, width) => new Markdown4(markdown, 0, 0, getMarkdownTheme7()).render(Math.max(1, width));
1607
+ var renderMarkdown = (markdown, width) => new Markdown4(markdown, 0, 0, getMarkdownTheme6()).render(Math.max(1, width));
1616
1608
  var firstStringArgument = (parameters, names) => {
1617
1609
  for (const name of names) {
1618
1610
  const value = parameters[name];
@@ -1767,7 +1759,6 @@ var campUiTranscriptBlockRenderer = {
1767
1759
 
1768
1760
  // packages/host-ui/src/pi/blocks/campUiDetail.ts
1769
1761
  var defaultCampUiDetailBlockRenderers = [
1770
- campUiEquipmentBlockRenderer,
1771
1762
  campUiTranscriptBlockRenderer,
1772
1763
  campUiPendingAskBlockRenderer
1773
1764
  ];
@@ -1906,9 +1897,169 @@ var makeCampUiDetailComponent = (context, registry = defaultCampUiDetailBlockRen
1906
1897
  };
1907
1898
  };
1908
1899
 
1909
- // packages/host-ui/src/pi/layout/campUiChrome.ts
1900
+ // packages/host-ui/src/pi/layout/campUiActivity.ts
1910
1901
  import { getSelectListTheme as getSelectListTheme9 } from "@earendil-works/pi-coding-agent";
1911
- import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
1902
+ import { sliceByColumn, visibleWidth as visibleWidth3 } from "@earendil-works/pi-tui";
1903
+ var CampUiActivityAccess = {
1904
+ read: "read",
1905
+ edit: "edit",
1906
+ write: "write"
1907
+ };
1908
+ var accessByTool = /* @__PURE__ */ new Map([
1909
+ ["read", CampUiActivityAccess.read],
1910
+ ["edit", CampUiActivityAccess.edit],
1911
+ ["write", CampUiActivityAccess.write]
1912
+ ]);
1913
+ var accessRank = {
1914
+ read: 0,
1915
+ edit: 1,
1916
+ write: 2
1917
+ };
1918
+ var filePathArgument = (parameters) => {
1919
+ for (const name of ["path", "file_path"]) {
1920
+ const value = parameters[name];
1921
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
1922
+ }
1923
+ return void 0;
1924
+ };
1925
+ var splitPath2 = (path) => {
1926
+ const separator = path.lastIndexOf("/");
1927
+ if (separator < 0) return { directory: ".", name: path };
1928
+ if (separator === 0) return { directory: "/", name: path.slice(1) || path };
1929
+ return { directory: path.slice(0, separator), name: path.slice(separator + 1) || path };
1930
+ };
1931
+ var collectCampUiActivityTree = (transcript) => {
1932
+ const directories = /* @__PURE__ */ new Map();
1933
+ const nodes = /* @__PURE__ */ new Map();
1934
+ for (const message of transcript) {
1935
+ for (const content of message.content) {
1936
+ if (content.kind !== "tool-call") continue;
1937
+ const access = accessByTool.get(content.toolName);
1938
+ if (access === void 0) continue;
1939
+ const path = filePathArgument(content.arguments);
1940
+ if (path === void 0) continue;
1941
+ const existing = nodes.get(path);
1942
+ if (existing !== void 0) {
1943
+ existing.count += 1;
1944
+ if (accessRank[access] > accessRank[existing.access]) existing.access = access;
1945
+ continue;
1946
+ }
1947
+ const { directory, name } = splitPath2(path);
1948
+ const node = { path, directory, name, access, count: 1 };
1949
+ nodes.set(path, node);
1950
+ const group = directories.get(directory) ?? { path: directory, nodes: [] };
1951
+ if (!directories.has(directory)) directories.set(directory, group);
1952
+ group.nodes.push(node);
1953
+ }
1954
+ }
1955
+ return [...directories.values()].map((directory) => ({
1956
+ path: directory.path,
1957
+ nodes: directory.nodes.map((node) => ({ ...node }))
1958
+ }));
1959
+ };
1960
+ var CAMP_UI_ACTIVITY_MAX_VISIBLE_PATHS = 12;
1961
+ var ACTIVITY_COLUMNS_PER_VISIBLE_PATH = 4;
1962
+ var flattenedNodes = (tree) => tree.flatMap((directory) => directory.nodes);
1963
+ var renderedRowCount = (tree, selected, hidden) => {
1964
+ const directories = tree.filter(
1965
+ (directory) => directory.nodes.some((node) => selected.has(node))
1966
+ ).length;
1967
+ return selected.size + directories + (hidden > 0 ? 1 : 0);
1968
+ };
1969
+ var selectVisibleNodes2 = (tree, width, height) => {
1970
+ const nodes = flattenedNodes(tree);
1971
+ const selected = /* @__PURE__ */ new Set();
1972
+ const widthBudget = Math.max(
1973
+ 1,
1974
+ Math.floor(Math.max(0, width) / ACTIVITY_COLUMNS_PER_VISIBLE_PATH)
1975
+ );
1976
+ const nodeBudget = Math.min(CAMP_UI_ACTIVITY_MAX_VISIBLE_PATHS, widthBudget);
1977
+ const mutations = nodes.filter((node) => node.access !== CampUiActivityAccess.read);
1978
+ const reads = nodes.filter((node) => node.access === CampUiActivityAccess.read);
1979
+ const addVisible = (node) => {
1980
+ if (selected.size >= nodeBudget) return;
1981
+ const candidate = new Set(selected).add(node);
1982
+ const hidden = nodes.length - candidate.size;
1983
+ if (renderedRowCount(tree, candidate, hidden) <= Math.max(0, height)) selected.add(node);
1984
+ };
1985
+ for (const node of mutations) addVisible(node);
1986
+ if (mutations.some((node) => !selected.has(node))) return selected;
1987
+ for (const node of reads) addVisible(node);
1988
+ return selected;
1989
+ };
1990
+ var shortenTail = (value, width) => {
1991
+ const available = Math.max(0, Math.floor(width));
1992
+ const valueWidth = visibleWidth3(value);
1993
+ if (valueWidth <= available) return value;
1994
+ if (available <= 0) return "";
1995
+ if (available === 1) return "\u2026";
1996
+ return `\u2026${sliceByColumn(value, valueWidth - available + 1, available - 1, true)}`;
1997
+ };
1998
+ var accessPresentation = {
1999
+ read: { label: "read", glyph: "r", tone: CampUiTone.neutral },
2000
+ edit: { label: "edited", glyph: "~", tone: CampUiTone.attention },
2001
+ write: { label: "created", glyph: "+", tone: CampUiTone.success }
2002
+ };
2003
+ var nodeLine2 = (node, last, width) => {
2004
+ const theme = getSelectListTheme9();
2005
+ const presentation = accessPresentation[node.access];
2006
+ const count = node.count > 1 ? ` \xD7${String(node.count)}` : "";
2007
+ const suffix = ` \xB7 ${presentation.label}${count}`;
2008
+ const branch = last ? "\u2514\u2500" : "\u251C\u2500";
2009
+ const nameWidth = Math.max(1, width - visibleWidth3(`${branch} ${presentation.glyph} ${suffix}`));
2010
+ const name = shortenTail(node.name, nameWidth);
2011
+ return fitCampUiLine(
2012
+ `${theme.description(branch)} ${renderCampUiStatus(name, presentation.tone, presentation.glyph)}${theme.description(suffix)}`,
2013
+ width
2014
+ );
2015
+ };
2016
+ var hiddenSummary = (nodes) => {
2017
+ const parts = Object.values(CampUiActivityAccess).flatMap((access) => {
2018
+ const count = nodes.filter((node) => node.access === access).length;
2019
+ if (count === 0) return [];
2020
+ const label = accessPresentation[access].label;
2021
+ return [`${String(count)} ${label} ${count === 1 ? "path" : "paths"}`];
2022
+ });
2023
+ return `... ${parts.join(" \xB7 ")} hidden`;
2024
+ };
2025
+ var renderCampUiActivityTree = (transcript, width, height) => {
2026
+ const theme = getSelectListTheme9();
2027
+ const tree = collectCampUiActivityTree(transcript);
2028
+ const nodes = flattenedNodes(tree);
2029
+ if (height <= 0) return [];
2030
+ if (nodes.length === 0) return [fitCampUiLine(theme.description("no file activity"), width)];
2031
+ const selected = selectVisibleNodes2(tree, width, height);
2032
+ const lines = [];
2033
+ for (const directory of tree) {
2034
+ const visible = directory.nodes.filter((node) => selected.has(node));
2035
+ if (visible.length === 0) continue;
2036
+ lines.push(fitCampUiLine(theme.description(shortenTail(directory.path, width)), width));
2037
+ visible.forEach((node, index) => {
2038
+ lines.push(nodeLine2(node, index === visible.length - 1, width));
2039
+ });
2040
+ }
2041
+ const hidden = nodes.filter((node) => !selected.has(node));
2042
+ if (hidden.length > 0) {
2043
+ lines.push(fitCampUiLine(theme.description(hiddenSummary(hidden)), width));
2044
+ }
2045
+ return lines;
2046
+ };
2047
+ var makeCampUiActivity = (context) => ({
2048
+ render: (viewport) => {
2049
+ const model = context.readModel();
2050
+ const workerId = model.state.selectedWorkerId;
2051
+ const worker = workerId === void 0 ? void 0 : findCampUiWorker(model.snapshot, workerId);
2052
+ return renderCampUiActivityTree(
2053
+ worker?.snapshot.session?.transcript ?? [],
2054
+ viewport.width,
2055
+ viewport.height
2056
+ );
2057
+ }
2058
+ });
2059
+
2060
+ // packages/host-ui/src/pi/layout/campUiChrome.ts
2061
+ import { getSelectListTheme as getSelectListTheme10 } from "@earendil-works/pi-coding-agent";
2062
+ import { truncateToWidth as truncateToWidth4, visibleWidth as visibleWidth4 } from "@earendil-works/pi-tui";
1912
2063
 
1913
2064
  // packages/host-ui/src/pi/shared/campUiPulse.ts
1914
2065
  var BRAILLE_BASE = 10240;
@@ -2000,7 +2151,7 @@ var campUiLevelMarkerText = (level, color) => level === CampUiLevel.resting ? le
2000
2151
  var makeCampUiCampHeader = (context) => ({
2001
2152
  render: (viewport) => {
2002
2153
  const snapshot = context.readModel().snapshot;
2003
- const theme = getSelectListTheme9();
2154
+ const theme = getSelectListTheme10();
2004
2155
  const mode = snapshot.placement === "external" ? "agentless" : "foreman";
2005
2156
  return [
2006
2157
  fitCampUiLine(theme.description(snapshot.displayName), viewport.width),
@@ -2021,7 +2172,7 @@ var rosterWidths = (width) => {
2021
2172
  };
2022
2173
  var rightColumn = (text, width) => {
2023
2174
  const fitted = truncateToWidth4(text, width);
2024
- return `${" ".repeat(Math.max(0, width - visibleWidth3(fitted)))}${fitted}`;
2175
+ return `${" ".repeat(Math.max(0, width - visibleWidth4(fitted)))}${fitted}`;
2025
2176
  };
2026
2177
  var pulseSamples = (timestamps, sampleCount, now) => {
2027
2178
  if (sampleCount <= 0) return [];
@@ -2051,13 +2202,13 @@ var usageText = (entry, width) => {
2051
2202
  const cache = usage?.latestCacheHitRate === void 0 ? usage === void 0 || usage.tokens.total === 0 ? "n/a" : "n/r" : percentText(usage.latestCacheHitRate);
2052
2203
  const cost = usage === void 0 ? "\u2014" : `$${usage.cost.toFixed(3)}`;
2053
2204
  const full = `${context} ${cache} ${cost}`;
2054
- if (visibleWidth3(full) <= width || usage === void 0) return full;
2205
+ if (visibleWidth4(full) <= width || usage === void 0) return full;
2055
2206
  const compactCost = `$${usage.cost.toFixed(2).replace(/^0/u, "")}`;
2056
2207
  return `${context}${cache}${compactCost}`;
2057
2208
  };
2058
2209
  var stateText = (state, duration, width) => {
2059
2210
  const full = `${state} ${duration}`;
2060
- return visibleWidth3(full) <= width ? full : state;
2211
+ return visibleWidth4(full) <= width ? full : state;
2061
2212
  };
2062
2213
  var modelText = (entry) => {
2063
2214
  const model = entry.snapshot.session?.usage?.model ?? entry.snapshot.worker.equipment.model;
@@ -2138,7 +2289,7 @@ var makeCampUiRoster = (context) => ({
2138
2289
  const model = context.readModel();
2139
2290
  const external = model.snapshot.placement === CampUiPlacement.external;
2140
2291
  if (model.snapshot.workers.length === 0 && !external) {
2141
- return [getSelectListTheme9().description("No workers")];
2292
+ return [getSelectListTheme10().description("No workers")];
2142
2293
  }
2143
2294
  const controller = external ? renderExternalControllerCard(model.snapshot.controllerConnected, viewport.width) : [];
2144
2295
  const workers = model.snapshot.workers.flatMap((entry) => {
@@ -2173,7 +2324,7 @@ var makeCampUiRoster = (context) => ({
2173
2324
  var makeCampUiSessionHeader = (context) => ({
2174
2325
  render: (viewport) => {
2175
2326
  const model = context.readModel();
2176
- const theme = getSelectListTheme9();
2327
+ const theme = getSelectListTheme10();
2177
2328
  const workerId = model.state.selectedWorkerId;
2178
2329
  if (workerId === void 0) {
2179
2330
  return [fitCampUiLine(theme.description("Select a worker"), viewport.width)];
@@ -2194,29 +2345,63 @@ var makeCampUiSessionHeader = (context) => ({
2194
2345
  ] : []
2195
2346
  });
2196
2347
 
2197
- // packages/host-ui/src/pi/layout/campUiFooter.ts
2198
- import { getMarkdownTheme as getMarkdownTheme8, getSelectListTheme as getSelectListTheme10 } from "@earendil-works/pi-coding-agent";
2199
- var aggregateCampUsage = (workers) => {
2200
- const usages = workers.flatMap(
2201
- (worker) => worker.snapshot.session?.usage === void 0 ? [] : [worker.snapshot.session.usage]
2348
+ // packages/host-ui/src/pi/layout/campUiEquipment.ts
2349
+ import { getSelectListTheme as getSelectListTheme11 } from "@earendil-works/pi-coding-agent";
2350
+ import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
2351
+ var fullEquipmentLines = (equipment, revision, width) => {
2352
+ if (width <= 0) return [];
2353
+ const theme = getSelectListTheme11();
2354
+ return equipmentDescriptionLines(equipment, revision).flatMap(
2355
+ (line) => wrapTextWithAnsi(theme.description(line), width).map(
2356
+ (wrapped) => fitCampUiLine(wrapped, width)
2357
+ )
2202
2358
  );
2359
+ };
2360
+ var renderCampUiEquipment = (equipment, revision, width, height) => {
2361
+ const lines = fullEquipmentLines(equipment, revision, width);
2362
+ const available = Math.max(0, Math.floor(height));
2363
+ if (available === 0 || lines.length === 0) return [];
2364
+ if (lines.length <= available) return lines;
2365
+ const shown = lines.slice(0, Math.max(0, available - 1));
2366
+ const hidden = lines.length - shown.length;
2367
+ const summary = getSelectListTheme11().description(
2368
+ `... ${String(hidden)} equipment ${hidden === 1 ? "line" : "lines"} hidden`
2369
+ );
2370
+ return [...shown, fitCampUiLine(summary, width)];
2371
+ };
2372
+ var makeCampUiEquipment = (context) => {
2373
+ const selectedWorker = () => {
2374
+ const model = context.readModel();
2375
+ const workerId = model.state.selectedWorkerId;
2376
+ return workerId === void 0 ? void 0 : findCampUiWorker(model.snapshot, workerId);
2377
+ };
2378
+ const fullLines = (width) => {
2379
+ const worker = selectedWorker();
2380
+ return worker === void 0 ? [fitCampUiLine(getSelectListTheme11().description("no worker selected"), width)] : fullEquipmentLines(
2381
+ worker.snapshot.worker.equipment,
2382
+ worker.snapshot.worker.equipmentVersion,
2383
+ width
2384
+ );
2385
+ };
2203
2386
  return {
2204
- cost: usages.reduce((total, usage) => total + usage.cost, 0),
2205
- reportedWorkers: usages.length,
2206
- tokens: usages.reduce((total, usage) => total + usage.tokens.total, 0),
2207
- totalWorkers: workers.length
2387
+ preferredRows: (width) => fullLines(width).length,
2388
+ render: (viewport) => {
2389
+ const worker = selectedWorker();
2390
+ if (worker === void 0) return fullLines(viewport.width).slice(0, viewport.height);
2391
+ return renderCampUiEquipment(
2392
+ worker.snapshot.worker.equipment,
2393
+ worker.snapshot.worker.equipmentVersion,
2394
+ viewport.width,
2395
+ viewport.height
2396
+ );
2397
+ }
2208
2398
  };
2209
2399
  };
2210
- var aggregateUsageText = (usage) => {
2211
- const partial = usage.reportedWorkers < usage.totalWorkers;
2212
- const coverage = partial ? ` \xB7 ${usage.reportedWorkers}/${usage.totalWorkers} reported` : "";
2213
- if (usage.totalWorkers > 0 && usage.reportedWorkers === 0) {
2214
- return `tokens unknown \xB7 cost unknown${coverage}`;
2215
- }
2216
- const lowerBound = partial ? "\u2265" : "";
2217
- return `\u2193 ${lowerBound}${compactNumber(usage.tokens)} tokens \xB7 ${lowerBound}$${usage.cost.toFixed(3)}${coverage}`;
2218
- };
2219
- var footerDivider = (width) => fitCampUiLine(getMarkdownTheme8().codeBlockBorder("\u2500".repeat(width)), width);
2400
+
2401
+ // packages/host-ui/src/pi/layout/campUiFooter.ts
2402
+ import { getMarkdownTheme as getMarkdownTheme7, getSelectListTheme as getSelectListTheme12 } from "@earendil-works/pi-coding-agent";
2403
+ import { sliceByColumn as sliceByColumn2, visibleWidth as visibleWidth5 } from "@earendil-works/pi-tui";
2404
+ var footerDivider = (width) => fitCampUiLine(getMarkdownTheme7().codeBlockBorder("\u2500".repeat(width)), width);
2220
2405
  var retryText = (retryAt, attempt, now) => {
2221
2406
  if (retryAt === void 0) return attempt === void 0 ? "" : `attempt ${String(attempt)}`;
2222
2407
  const seconds = Math.max(0, Math.ceil((retryAt - now) / 1e3));
@@ -2227,56 +2412,73 @@ var diagnosticText = (value) => {
2227
2412
  const normalized = value?.replaceAll(/\s+/gu, " ").trim();
2228
2413
  return normalized === "" ? void 0 : normalized;
2229
2414
  };
2230
- var makeCampUiCampFooter = (context) => ({
2415
+ var workerCountText = (workers) => {
2416
+ if (workers.length === 0) return "no workers";
2417
+ const working = workers.filter(
2418
+ (worker) => worker.snapshot.worker.state === CampWorkerState.running
2419
+ ).length;
2420
+ const waiting = workers.filter((worker) => worker.asks.some(isPendingAsk)).length;
2421
+ const parts = [
2422
+ `${String(workers.length)} ${workers.length === 1 ? "worker" : "workers"}`,
2423
+ ...working === 0 ? [] : [`${String(working)} working`],
2424
+ ...waiting === 0 ? [] : [`${String(waiting)} waiting`]
2425
+ ];
2426
+ return parts.join(" \xB7 ");
2427
+ };
2428
+ var isPendingAsk = (ask) => ask.state === "pending";
2429
+ var shortenPath = (value, width) => {
2430
+ const available = Math.max(0, Math.floor(width));
2431
+ if (available <= 0) return "";
2432
+ if (visibleWidth5(value) <= available) return value;
2433
+ if (available === 1) return "\u2026";
2434
+ const kept = available - 1;
2435
+ return `\u2026${sliceByColumn2(value, visibleWidth5(value) - kept, kept, true)}`;
2436
+ };
2437
+ var makeCampUiFooter = (context) => ({
2231
2438
  render: (viewport) => {
2232
2439
  const model = context.readModel();
2233
- const theme = getSelectListTheme10();
2234
- const health = model.snapshot.health;
2235
- const usage = aggregateUsageText(aggregateCampUsage(model.snapshot.workers));
2440
+ const theme = getSelectListTheme12();
2441
+ const snapshot = model.snapshot;
2442
+ const health = snapshot.health;
2236
2443
  const actionError = diagnosticText(model.state.lastActionError);
2237
2444
  const healthSummary = diagnosticText(health.message);
2238
2445
  const healthMessage = health.code === void 0 ? healthSummary : healthSummary === void 0 ? health.code : `${health.code} \xB7 ${healthSummary}`;
2239
2446
  const healthTone = health.status === CampUiHealthStatus.failed ? CampUiTone.error : CampUiTone.attention;
2240
- 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);
2241
2447
  const retry = retryText(health.retryAt, health.attempt, model.state.presentationNow);
2242
- return [
2243
- footerDivider(viewport.width),
2244
- joinCampUiColumns(diagnostic, theme.description(usage), viewport.width),
2245
- fitCampUiLine(retry === "" ? "" : theme.description(retry), viewport.width)
2448
+ 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);
2449
+ const branch = snapshot.projectBranch === null ? "" : ` \xB7 ${snapshot.projectBranch}`;
2450
+ const version = `v${snapshot.hostVersion}`;
2451
+ const candidates = [
2452
+ { right: [workerCountText(snapshot.workers), version, retry], branch },
2453
+ { right: [workerCountText(snapshot.workers), version], branch },
2454
+ { right: [version], branch },
2455
+ { right: [version], branch: "" }
2246
2456
  ];
2247
- }
2248
- });
2249
- var makeCampUiSessionFooter = (context) => ({
2250
- render: (viewport) => {
2251
- const model = context.readModel();
2252
- const theme = getSelectListTheme10();
2253
- const workerId = model.state.selectedWorkerId;
2254
- const worker = workerId === void 0 ? void 0 : findCampUiWorker(model.snapshot, workerId);
2255
- const usage = worker?.snapshot.session?.usage;
2256
- const tokenText = usage === void 0 ? "tokens unknown" : `\u2193 ${compactNumber(usage.tokens.total)} tokens`;
2257
- const costText = usage === void 0 ? "cost unknown" : `$${usage.cost.toFixed(3)}`;
2258
- const cacheText = usage?.latestCacheHitRate === void 0 ? usage === void 0 || usage.tokens.total === 0 ? "cache n/a" : "cache not reported" : `cache ${usage.latestCacheHitRate.toFixed(0)}%`;
2259
- const contextText = usage?.context?.percent === null || usage?.context?.percent === void 0 ? "context unknown" : `context ${usage.context.percent.toFixed(0)}%`;
2260
- const modelText2 = usage?.model ?? worker?.snapshot.worker.equipment.model ?? "model unknown";
2261
- const thinking = worker?.snapshot.session?.thinkingLevel ?? worker?.snapshot.worker.equipment.thinkingLevel ?? "unknown";
2262
- 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}`)}`;
2263
- const branch = worker === void 0 || model.snapshot.projectBranch === null ? "" : theme.description(`branch ${model.snapshot.projectBranch}`);
2457
+ const MINIMUM_PATH_COLUMNS = 8;
2458
+ const chosen = candidates.find((candidate) => {
2459
+ const rightWidth = visibleWidth5(candidate.right.filter((part) => part !== "").join(" \xB7 "));
2460
+ const room = viewport.width - rightWidth - 3 - visibleWidth5(candidate.branch);
2461
+ return room >= MINIMUM_PATH_COLUMNS;
2462
+ }) ?? candidates[candidates.length - 1];
2463
+ const right = (chosen ?? candidates[0])?.right.filter((part) => part !== "").join(" \xB7 ") ?? "";
2464
+ const chosenBranch = chosen?.branch ?? "";
2465
+ const leftBudget = Math.max(0, viewport.width - visibleWidth5(right) - 3);
2466
+ const path = shortenPath(
2467
+ snapshot.projectScope,
2468
+ Math.max(0, leftBudget - visibleWidth5(chosenBranch))
2469
+ );
2470
+ const left = diagnostic === "" ? theme.description(`${path}${chosenBranch}`) : `${diagnostic} ${theme.description(`\xB7 ${path}${chosenBranch}`)}`;
2264
2471
  return [
2265
2472
  footerDivider(viewport.width),
2266
- branch === "" ? fitCampUiLine(identity, viewport.width) : joinCampUiColumns(identity, branch, viewport.width),
2267
- joinCampUiColumns(
2268
- theme.description(`${tokenText} \xB7 ${costText} \xB7 ${cacheText} \xB7 ${contextText}`),
2269
- theme.description(`${modelText2} \xB7 effort ${thinking}`),
2270
- viewport.width
2271
- )
2473
+ joinCampUiColumns(left, theme.description(right), viewport.width)
2272
2474
  ];
2273
2475
  }
2274
2476
  });
2275
2477
 
2276
2478
  // packages/host-ui/src/pi/layout/campUiResources.ts
2277
- import { getSelectListTheme as getSelectListTheme11 } from "@earendil-works/pi-coding-agent";
2479
+ import { getSelectListTheme as getSelectListTheme13 } from "@earendil-works/pi-coding-agent";
2278
2480
  var renderResourceTree = (label, value, tone, width) => {
2279
- const theme = getSelectListTheme11();
2481
+ const theme = getSelectListTheme13();
2280
2482
  const lines = [
2281
2483
  theme.description(label),
2282
2484
  `${theme.description("\u2514\u2500")} ${renderCampUiStatus(value, tone)}`
@@ -2284,6 +2486,7 @@ var renderResourceTree = (label, value, tone, width) => {
2284
2486
  return lines.map((line) => fitCampUiLine(line, width));
2285
2487
  };
2286
2488
  var makeCampUiResources = (context) => ({
2489
+ preferredRows: () => 10,
2287
2490
  render: (viewport) => {
2288
2491
  const model = context.readModel();
2289
2492
  const snapshot = model.snapshot;
@@ -2317,27 +2520,29 @@ var makeCampUiResources = (context) => ({
2317
2520
  ...missingMcpHealth ? ["health not reported"] : []
2318
2521
  ];
2319
2522
  const mcpText = !equipmentReported ? "not reported" : mcpNames.length === 0 ? "none configured" : `${mcpNames.join(", ")}${mcpNotes.length === 0 ? "" : ` \xB7 ${mcpNotes.join(", ")}`}`;
2320
- return [
2523
+ const contentWidth = Math.max(1, viewport.width - 2);
2524
+ const body = [
2321
2525
  ...renderResourceTree(
2322
2526
  "Context",
2323
2527
  resourceText(inventory.contextFiles),
2324
2528
  inventory.contextFiles.length > 0 ? CampUiTone.success : CampUiTone.neutral,
2325
- viewport.width
2529
+ contentWidth
2326
2530
  ),
2327
2531
  ...renderResourceTree(
2328
2532
  "Extensions",
2329
2533
  resourceText(inventory.extensions),
2330
2534
  inventory.extensions.length > 0 ? CampUiTone.success : CampUiTone.neutral,
2331
- viewport.width
2535
+ contentWidth
2332
2536
  ),
2333
2537
  ...renderResourceTree(
2334
2538
  "Skills",
2335
2539
  resourceText(inventory.skills),
2336
2540
  inventory.skills.length > 0 ? CampUiTone.success : CampUiTone.neutral,
2337
- viewport.width
2541
+ contentWidth
2338
2542
  ),
2339
- ...renderResourceTree("MCP", mcpText, mcpTone, viewport.width)
2543
+ ...renderResourceTree("MCP", mcpText, mcpTone, contentWidth)
2340
2544
  ];
2545
+ return renderCampUiPanel("Camp inventory \xB7 Esc to close", body, viewport.width);
2341
2546
  }
2342
2547
  });
2343
2548
 
@@ -2349,9 +2554,14 @@ var makeDefaultCampUiContributions = (tui, detailBlocks) => [
2349
2554
  mount: (context) => succeed2(makeCampUiCampHeader(context))
2350
2555
  },
2351
2556
  {
2352
- id: "default-resources",
2353
- slotId: CampUiSlot.resources,
2354
- mount: (context) => succeed2(makeCampUiResources(context))
2557
+ id: "default-activity",
2558
+ slotId: CampUiSlot.activity,
2559
+ mount: (context) => succeed2(makeCampUiActivity(context))
2560
+ },
2561
+ {
2562
+ id: "default-equipment",
2563
+ slotId: CampUiSlot.equipment,
2564
+ mount: (context) => succeed2(makeCampUiEquipment(context))
2355
2565
  },
2356
2566
  {
2357
2567
  id: "default-roster",
@@ -2374,29 +2584,29 @@ var makeDefaultCampUiContributions = (tui, detailBlocks) => [
2374
2584
  mount: (context) => succeed2(makeCampUiComposerComponent(context, tui))
2375
2585
  },
2376
2586
  {
2377
- id: "default-camp-footer",
2378
- slotId: CampUiSlot.campFooter,
2379
- mount: (context) => succeed2(makeCampUiCampFooter(context))
2587
+ id: "default-footer",
2588
+ slotId: CampUiSlot.footer,
2589
+ mount: (context) => succeed2(makeCampUiFooter(context))
2380
2590
  },
2381
2591
  {
2382
- id: "default-session-footer",
2383
- slotId: CampUiSlot.sessionFooter,
2384
- mount: (context) => succeed2(makeCampUiSessionFooter(context))
2592
+ id: "default-inventory-overlay",
2593
+ slotId: CampUiSlot.overlay,
2594
+ mount: (context) => succeed2(makeCampUiResources(context))
2385
2595
  }
2386
2596
  ];
2387
2597
 
2388
2598
  // packages/host-ui/src/pi/layout/campUiSection.ts
2389
- import { getMarkdownTheme as getMarkdownTheme9, getSelectListTheme as getSelectListTheme12 } from "@earendil-works/pi-coding-agent";
2390
- import { visibleWidth as visibleWidth4 } from "@earendil-works/pi-tui";
2599
+ import { getMarkdownTheme as getMarkdownTheme8, getSelectListTheme as getSelectListTheme14 } from "@earendil-works/pi-coding-agent";
2600
+ import { visibleWidth as visibleWidth6 } from "@earendil-works/pi-tui";
2391
2601
  var renderHeader = (header, width) => {
2392
- const theme = getMarkdownTheme9();
2393
- const selectTheme = getSelectListTheme12();
2602
+ const theme = getMarkdownTheme8();
2603
+ const selectTheme = getSelectListTheme14();
2394
2604
  const rule2 = header.focused === true ? selectTheme.selectedPrefix : theme.codeBlockBorder;
2395
2605
  const title = header.focused === true ? selectTheme.selectedText : theme.heading;
2396
2606
  const ruleCharacter = header.focused === true ? "\u2501" : "\u2500";
2397
2607
  const detail = header.detail === void 0 ? "" : ` ${selectTheme.description(header.detail)}`;
2398
2608
  const label = `${rule2(ruleCharacter.repeat(2))} ${title(header.title)}${detail} `;
2399
- const ruleWidth = Math.max(0, width - visibleWidth4(label));
2609
+ const ruleWidth = Math.max(0, width - visibleWidth6(label));
2400
2610
  return fitCampUiLine(`${label}${rule2(ruleCharacter.repeat(ruleWidth))}`, width);
2401
2611
  };
2402
2612
  var makeCampUiSection = (options) => {
@@ -2408,6 +2618,7 @@ var makeCampUiSection = (options) => {
2408
2618
  const disposableComponents = components.some((component) => component.dispose !== void 0);
2409
2619
  return {
2410
2620
  sectionSize: options.size,
2621
+ ...options.preferredRows === void 0 ? {} : { preferredRows: options.preferredRows },
2411
2622
  ...components.some((component) => component.focusable === true) ? { focusable: true } : {},
2412
2623
  render: (viewport) => {
2413
2624
  if (viewport.height <= 0) {
@@ -2478,18 +2689,33 @@ var decorateDefaultCampUiSections = (context, slots) => {
2478
2689
  })
2479
2690
  );
2480
2691
  decorated.set(
2481
- CampUiSlot.resources,
2692
+ CampUiSlot.activity,
2482
2693
  makeCampUiSection({
2483
- header: () => ({ title: "Resources" }),
2484
- size: defaultCampUiLeftSectionSizes.resources,
2694
+ header: () => ({ title: "Activity" }),
2695
+ size: defaultCampUiRightSectionSizes.activity,
2485
2696
  children: [
2486
2697
  {
2487
- component: component(CampUiSlot.resources),
2698
+ component: component(CampUiSlot.activity),
2488
2699
  size: { kind: CampUiSectionSizeKind.grow, weight: 1 }
2489
2700
  }
2490
2701
  ]
2491
2702
  })
2492
2703
  );
2704
+ const equipment = component(CampUiSlot.equipment);
2705
+ decorated.set(
2706
+ CampUiSlot.equipment,
2707
+ makeCampUiSection({
2708
+ header: () => ({ title: "Equipment" }),
2709
+ size: defaultCampUiRightSectionSizes.equipment,
2710
+ children: [
2711
+ {
2712
+ component: equipment,
2713
+ size: { kind: CampUiSectionSizeKind.grow, weight: 1 }
2714
+ }
2715
+ ],
2716
+ preferredRows: (width) => 1 + (equipment.preferredRows?.(width) ?? defaultCampUiRightSectionSizes.equipment.rows - 1)
2717
+ })
2718
+ );
2493
2719
  decorated.set(
2494
2720
  CampUiSlot.roster,
2495
2721
  makeCampUiSection({
@@ -2515,7 +2741,7 @@ import { initTheme } from "@earendil-works/pi-coding-agent";
2515
2741
  import { ProcessTerminal, TUI } from "@earendil-works/pi-tui";
2516
2742
 
2517
2743
  // packages/host-ui/src/pi/piCampUiRoot.ts
2518
- import { getMarkdownTheme as getMarkdownTheme10 } from "@earendil-works/pi-coding-agent";
2744
+ import { getMarkdownTheme as getMarkdownTheme9 } from "@earendil-works/pi-coding-agent";
2519
2745
  import { Key as Key3, matchesKey as matchesKey3 } from "@earendil-works/pi-tui";
2520
2746
 
2521
2747
  // packages/host-ui/src/pi/runtime/campUiPointerInput.ts
@@ -2644,7 +2870,7 @@ var PiCampUiRoot = class {
2644
2870
  segments.push({
2645
2871
  x: divider.x,
2646
2872
  width: 1,
2647
- text: getMarkdownTheme10().codeBlockBorder("\u2502")
2873
+ text: getMarkdownTheme9().codeBlockBorder("\u2502")
2648
2874
  });
2649
2875
  }
2650
2876
  }
@@ -2691,6 +2917,14 @@ var PiCampUiRoot = class {
2691
2917
  if (focusedSlotId !== model.state.focusedSlotId) {
2692
2918
  this.context.emit(CampUiIntents.FocusSlot({ slotId: focusedSlotId }));
2693
2919
  }
2920
+ if (model.state.inventoryVisible && matchesKey3(input, Key3.escape)) {
2921
+ this.context.emit(CampUiIntents.SetInventoryVisible({ visible: false }));
2922
+ return;
2923
+ }
2924
+ if (focusedSlotId !== CampUiSlot.composer && input === "i") {
2925
+ this.context.emit(CampUiIntents.SetInventoryVisible({ visible: true }));
2926
+ return;
2927
+ }
2694
2928
  if (this.slots.get(focusedSlotId)?.handleInput?.(input) === true) return;
2695
2929
  if (matchesKey3(input, Key3.escape)) {
2696
2930
  if (focusedSlotId === CampUiSlot.composer) {
@@ -2776,19 +3010,67 @@ var PiCampUiRoot = class {
2776
3010
  }
2777
3011
  };
2778
3012
 
3013
+ // packages/host-ui/src/pi/runtime/campUiFrameScheduler.ts
3014
+ var DEFAULT_MINIMUM_INTERVAL_MS = 1e3;
3015
+ var DEFAULT_MAXIMUM_INTERVAL_MS = 8e3;
3016
+ var DEFAULT_EXPENSIVE_FRAME_MS = 120;
3017
+ var CAMP_UI_REDUCED_MOTION_ENV = "PRIIISK_REDUCED_MOTION";
3018
+ var campUiReducedMotionFromEnv = (environment) => {
3019
+ const value = environment[CAMP_UI_REDUCED_MOTION_ENV]?.trim().toLowerCase();
3020
+ return value !== void 0 && value !== "" && value !== "0" && value !== "false";
3021
+ };
3022
+ var makeCampUiFrameScheduler = (options = {}) => gen(function* () {
3023
+ const minimum = Math.max(
3024
+ 1,
3025
+ Math.floor(options.minimumIntervalMs ?? DEFAULT_MINIMUM_INTERVAL_MS)
3026
+ );
3027
+ const maximum = Math.max(
3028
+ minimum,
3029
+ Math.floor(options.maximumIntervalMs ?? DEFAULT_MAXIMUM_INTERVAL_MS)
3030
+ );
3031
+ const expensive = Math.max(
3032
+ 1,
3033
+ Math.floor(options.expensiveFrameMs ?? DEFAULT_EXPENSIVE_FRAME_MS)
3034
+ );
3035
+ const reducedMotion = options.reducedMotion === true;
3036
+ const pending = yield* make(false);
3037
+ const interval = yield* make(minimum);
3038
+ return {
3039
+ request: reducedMotion ? _void : set(pending, true),
3040
+ intervalMs: get(interval),
3041
+ run: (frame) => reducedMotion ? never : forever(
3042
+ gen(function* () {
3043
+ yield* sleep(millis(yield* get(interval)));
3044
+ const requested = yield* getAndSet(pending, false);
3045
+ if (!requested) return;
3046
+ const startedAt = yield* currentTimeMillis;
3047
+ yield* frame;
3048
+ const cost = (yield* currentTimeMillis) - startedAt;
3049
+ yield* update(
3050
+ interval,
3051
+ (current) => cost > expensive ? Math.min(maximum, current * 2) : Math.max(minimum, Math.floor(current / 2))
3052
+ );
3053
+ })
3054
+ )
3055
+ };
3056
+ });
3057
+
2779
3058
  // packages/host-ui/src/pi/runtime/campUiPresentationClock.ts
2780
3059
  var hasPendingCampUiAsk = (model) => model.snapshot.workers.some(
2781
3060
  (worker) => worker.asks.some((ask) => ask.state === CampAskState.pending)
2782
3061
  );
2783
- var runCampUiPresentationClock = (readModel, controller) => forever(
2784
- sleep("1 second").pipe(
2785
- zipRight(
2786
- suspend(
2787
- () => hasPendingCampUiAsk(readModel()) ? currentTimeMillis.pipe(flatMap(controller.setPresentationNow)) : _void
2788
- )
2789
- )
2790
- )
2791
- );
3062
+ var runCampUiPresentationClock = (readModel, controller, scheduler) => gen(function* () {
3063
+ const frames = scheduler ?? (yield* makeCampUiFrameScheduler({
3064
+ reducedMotion: campUiReducedMotionFromEnv(process.env)
3065
+ }));
3066
+ const advance = suspend(
3067
+ () => hasPendingCampUiAsk(readModel()) ? currentTimeMillis.pipe(flatMap(controller.setPresentationNow)) : _void
3068
+ );
3069
+ return yield* zipRight(
3070
+ frames.request,
3071
+ frames.run(zipRight(advance, frames.request))
3072
+ );
3073
+ });
2792
3074
 
2793
3075
  // packages/host-ui/src/pi/runtime/campUiSubscriptionRetry.ts
2794
3076
  var CAMP_UI_SUBSCRIPTION_MAX_RETRIES = 4;
@@ -2844,16 +3126,16 @@ var makeCampUiTerminalModes = (terminal) => {
2844
3126
  var closedOnPurpose = (exit) => isSuccess(exit) || isInterruptedOnly(exit.cause);
2845
3127
  var mountPiCampUi = (options) => gen(function* () {
2846
3128
  const startedAt = yield* currentTimeMillis;
2847
- const healthRef = yield* make3({
3129
+ const healthRef = yield* make4({
2848
3130
  status: CampUiRuntimeStatus.starting,
2849
3131
  operation: "ui.mount",
2850
3132
  updatedAt: startedAt
2851
3133
  });
2852
- const setHealth = (health) => get(healthRef).pipe(
3134
+ const setHealth = (health) => get2(healthRef).pipe(
2853
3135
  flatMap(
2854
3136
  (current2) => sameCampUiRuntimeHealth(current2, { ...health, updatedAt: current2.updatedAt }) ? _void : currentTimeMillis.pipe(
2855
3137
  flatMap(
2856
- (updatedAt) => set(healthRef, { ...health, updatedAt })
3138
+ (updatedAt) => set2(healthRef, { ...health, updatedAt })
2857
3139
  )
2858
3140
  )
2859
3141
  )
@@ -2906,6 +3188,22 @@ var mountPiCampUi = (options) => gen(function* () {
2906
3188
  );
2907
3189
  tui.addChild(root);
2908
3190
  tui.setFocus(root);
3191
+ const inventory = slots.get(CampUiSlot.overlay);
3192
+ const inventoryOverlay = inventory === void 0 ? void 0 : tui.showOverlay(
3193
+ {
3194
+ render: (width) => [
3195
+ ...inventory.render({ width, height: Math.max(0, terminal.rows) })
3196
+ ],
3197
+ invalidate: () => inventory.invalidate?.()
3198
+ },
3199
+ {
3200
+ width: "60%",
3201
+ minWidth: 36,
3202
+ margin: 1,
3203
+ visible: () => current.state.inventoryVisible,
3204
+ nonCapturing: true
3205
+ }
3206
+ );
2909
3207
  yield* sync(() => {
2910
3208
  terminal.setTitle(initial.displayName);
2911
3209
  terminal.clearScreen();
@@ -2922,6 +3220,9 @@ var mountPiCampUi = (options) => gen(function* () {
2922
3220
  )
2923
3221
  )
2924
3222
  );
3223
+ if (inventoryOverlay !== void 0) {
3224
+ yield* addFinalizer(() => sync(() => inventoryOverlay.hide()));
3225
+ }
2925
3226
  yield* forkScoped(
2926
3227
  gen(function* () {
2927
3228
  for (let attempt = 0; attempt < 25; attempt += 1) {
@@ -2930,7 +3231,7 @@ var mountPiCampUi = (options) => gen(function* () {
2930
3231
  }
2931
3232
  })
2932
3233
  );
2933
- const fibers = yield* make2();
3234
+ const fibers = yield* make3();
2934
3235
  yield* run(
2935
3236
  fibers,
2936
3237
  store.changes.pipe(
@@ -2994,12 +3295,12 @@ var mountPiCampUi = (options) => gen(function* () {
2994
3295
  return {
2995
3296
  controller: store.controller,
2996
3297
  tui,
2997
- health: { current: get(healthRef), changes: healthRef.changes }
3298
+ health: { current: get2(healthRef), changes: healthRef.changes }
2998
3299
  };
2999
3300
  });
3000
3301
 
3001
3302
  // packages/host-ui/src/pi/run-selector/campRunSelector.ts
3002
- import { getMarkdownTheme as getMarkdownTheme11, getSelectListTheme as getSelectListTheme13, initTheme as initTheme2 } from "@earendil-works/pi-coding-agent";
3303
+ import { getMarkdownTheme as getMarkdownTheme10, getSelectListTheme as getSelectListTheme15, initTheme as initTheme2 } from "@earendil-works/pi-coding-agent";
3003
3304
  import {
3004
3305
  Key as Key4,
3005
3306
  matchesKey as matchesKey4,
@@ -3021,8 +3322,8 @@ var PiCampRunSelectorRoot = class {
3021
3322
  render(width) {
3022
3323
  const height = Math.max(0, this.getHeight());
3023
3324
  const viewportWidth = Math.max(0, width - 1);
3024
- const markdown = getMarkdownTheme11();
3025
- const selection = getSelectListTheme13();
3325
+ const markdown = getMarkdownTheme10();
3326
+ const selection = getSelectListTheme15();
3026
3327
  const header = `${markdown.codeBlockBorder("\u2500\u2500")} ${markdown.heading("Resume camp")} ${markdown.codeBlockBorder("\u2500".repeat(Math.max(0, viewportWidth - 16)))}`;
3027
3328
  const available = Math.max(0, height - 2);
3028
3329
  const capacity = Math.max(1, Math.floor(available / 2));
@@ -3066,7 +3367,7 @@ var PiCampRunSelectorRoot = class {
3066
3367
  var selectCampRun = (options) => scoped(
3067
3368
  gen(function* () {
3068
3369
  yield* sync(() => initTheme2(void 0, false));
3069
- const selected = yield* make();
3370
+ const selected = yield* make2();
3070
3371
  const terminal = options.terminal ?? new ProcessTerminal2();
3071
3372
  const modes = makeCampUiTerminalModes(terminal);
3072
3373
  yield* acquireRelease(
@@ -3100,12 +3401,15 @@ var selectCampRun = (options) => scoped(
3100
3401
  // packages/host-ui/src/index.ts
3101
3402
  var PACKAGE = "@priiisk/host-ui";
3102
3403
  export {
3404
+ CAMP_UI_ACTIVITY_MAX_VISIBLE_PATHS,
3103
3405
  CAMP_UI_BASH_PREVIEW_LINES,
3104
3406
  CAMP_UI_DEFAULT_TOOL_PREVIEW_LINES,
3105
3407
  CAMP_UI_EXPLORED_COLLAPSED_PATHS,
3106
3408
  CAMP_UI_EXPLORED_TOOL_NAMES,
3409
+ CAMP_UI_REDUCED_MOTION_ENV,
3107
3410
  CAMP_UI_SUBSCRIPTION_BASE_DELAY_MS,
3108
3411
  CAMP_UI_SUBSCRIPTION_MAX_RETRIES,
3412
+ CampUiActivityAccess,
3109
3413
  CampUiAssistantMessage,
3110
3414
  CampUiBackend,
3111
3415
  CampUiBackendError,
@@ -3135,17 +3439,18 @@ export {
3135
3439
  campUiAskBlockId,
3136
3440
  campUiAskDetailBlock,
3137
3441
  campUiBlockCacheSize,
3138
- campUiEquipmentBlockRenderer,
3139
3442
  campUiExploredReadPath,
3140
3443
  campUiExploredReadRange,
3141
3444
  campUiMessageSignature,
3142
3445
  campUiPendingAskBlockRenderer,
3143
3446
  campUiPendingAskTargets,
3447
+ campUiReducedMotionFromEnv,
3144
3448
  campUiResultSignature,
3145
3449
  campUiSubscriptionRetryDelay,
3146
3450
  campUiToolResultCacheSize,
3147
3451
  campUiTranscriptBlockRenderer,
3148
3452
  campUiWorkerStateLabel,
3453
+ collectCampUiActivityTree,
3149
3454
  compactNumber,
3150
3455
  composeCampUiDetailBlocks,
3151
3456
  composeCampUiSlots,
@@ -3153,6 +3458,7 @@ export {
3153
3458
  defaultCampUiDetailBlockRenderers,
3154
3459
  defaultCampUiLayoutPolicy,
3155
3460
  defaultCampUiLeftSectionSizes,
3461
+ defaultCampUiRightSectionSizes,
3156
3462
  emptyCampUiComponent,
3157
3463
  findCampUiSpatialFocusTarget,
3158
3464
  findCampUiWorker,
@@ -3165,12 +3471,16 @@ export {
3165
3471
  isCampUiPendingAsk,
3166
3472
  joinCampUiColumns,
3167
3473
  jsonText,
3474
+ makeCampUiActivity,
3168
3475
  makeCampUiComponentGroup,
3169
3476
  makeCampUiComposerComponent,
3170
3477
  makeCampUiDetailComponent,
3478
+ makeCampUiEquipment,
3479
+ makeCampUiFrameScheduler,
3171
3480
  makeCampUiIntentHandler,
3172
3481
  makeCampUiIntentRouter,
3173
3482
  makeCampUiModel,
3483
+ makeCampUiResources,
3174
3484
  makeCampUiSection,
3175
3485
  makeCampUiStore,
3176
3486
  makeDefaultCampUiContributions,
@@ -3178,9 +3488,11 @@ export {
3178
3488
  makeInitialCampUiState,
3179
3489
  mountPiCampUi,
3180
3490
  nextCampUiPendingAskTarget,
3491
+ renderCampUiActivityTree,
3181
3492
  renderCampUiDetailBlockLayout,
3182
3493
  renderCampUiDetailBlocks,
3183
3494
  renderCampUiDirectedBlock,
3495
+ renderCampUiEquipment,
3184
3496
  renderCampUiExploredGroup,
3185
3497
  renderCampUiPanel,
3186
3498
  renderCampUiState,