pi-landstrip 0.18.9 → 0.18.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.ts +102 -5
  2. package/package.json +2 -2
package/dist/index.ts CHANGED
@@ -1865,6 +1865,7 @@ function taskDetails(task, state = task.state) {
1865
1865
  currentTool: task.currentTool,
1866
1866
  toolCalls: task.toolCalls,
1867
1867
  retryAttempt: task.retryAttempt,
1868
+ usage: task.usage ? { ...task.usage } : undefined,
1868
1869
  startedAt: task.startedAt,
1869
1870
  finishedAt: task.finishedAt,
1870
1871
  output: task.output,
@@ -1878,6 +1879,78 @@ function taskDuration(details) {
1878
1879
  const seconds = Math.max(0, end - details.startedAt) / 1000;
1879
1880
  return seconds < 10 ? `${seconds.toFixed(1)}s` : `${Math.round(seconds)}s`;
1880
1881
  }
1882
+ function usageNumber(value) {
1883
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
1884
+ }
1885
+ function isTaskUsage(value) {
1886
+ return isRecord2(value) && usageNumber(value.input) && usageNumber(value.output) && usageNumber(value.cacheRead) && usageNumber(value.cacheWrite) && usageNumber(value.cost) && usageNumber(value.turns) && Number.isInteger(value.turns);
1887
+ }
1888
+ function taskUsageFromMessage(message) {
1889
+ if (!isRecord2(message) || message.role !== "assistant" || !isRecord2(message.usage))
1890
+ return;
1891
+ const usage = message.usage;
1892
+ if (!usageNumber(usage.input) || !usageNumber(usage.output) || !usageNumber(usage.cacheRead) || !usageNumber(usage.cacheWrite) || !isRecord2(usage.cost) || !usageNumber(usage.cost.total)) {
1893
+ return;
1894
+ }
1895
+ return {
1896
+ input: usage.input,
1897
+ output: usage.output,
1898
+ cacheRead: usage.cacheRead,
1899
+ cacheWrite: usage.cacheWrite,
1900
+ cost: usage.cost.total,
1901
+ turns: 1
1902
+ };
1903
+ }
1904
+ function addTaskUsage(current, added) {
1905
+ return {
1906
+ input: (current?.input ?? 0) + added.input,
1907
+ output: (current?.output ?? 0) + added.output,
1908
+ cacheRead: (current?.cacheRead ?? 0) + added.cacheRead,
1909
+ cacheWrite: (current?.cacheWrite ?? 0) + added.cacheWrite,
1910
+ cost: (current?.cost ?? 0) + added.cost,
1911
+ turns: (current?.turns ?? 0) + added.turns
1912
+ };
1913
+ }
1914
+ function formatTokens(count) {
1915
+ if (count < 1000)
1916
+ return String(count);
1917
+ if (count < 1e4)
1918
+ return `${(count / 1000).toFixed(1)}k`;
1919
+ if (count < 1e6)
1920
+ return `${Math.round(count / 1000)}k`;
1921
+ if (count < 1e7)
1922
+ return `${(count / 1e6).toFixed(1)}M`;
1923
+ return `${Math.round(count / 1e6)}M`;
1924
+ }
1925
+ function formatTaskUsage(usage) {
1926
+ if (!usage)
1927
+ return;
1928
+ const parts = [];
1929
+ if (usage.turns)
1930
+ parts.push(`${usage.turns} turn${usage.turns === 1 ? "" : "s"}`);
1931
+ if (usage.input)
1932
+ parts.push(`in:${formatTokens(usage.input)}`);
1933
+ if (usage.output)
1934
+ parts.push(`out:${formatTokens(usage.output)}`);
1935
+ if (usage.cacheRead)
1936
+ parts.push(`R${formatTokens(usage.cacheRead)}`);
1937
+ if (usage.cacheWrite)
1938
+ parts.push(`W${formatTokens(usage.cacheWrite)}`);
1939
+ if (usage.cost)
1940
+ parts.push(`$${usage.cost.toFixed(4)}`);
1941
+ return parts.length > 0 ? parts.join(" ") : undefined;
1942
+ }
1943
+ function formatTaskUsageSummary(usage) {
1944
+ if (!usage)
1945
+ return;
1946
+ const parts = [];
1947
+ const tokens = usage.input + usage.output;
1948
+ if (tokens)
1949
+ parts.push(`${formatTokens(tokens)} tok`);
1950
+ if (usage.cost)
1951
+ parts.push(`$${usage.cost.toFixed(4)}`);
1952
+ return parts.length > 0 ? parts.join(" ") : undefined;
1953
+ }
1881
1954
  function activeTaskRecords(tasks) {
1882
1955
  const byId = new Map(tasks.map((task) => [task.id, task]));
1883
1956
  const visibleIds = new Set;
@@ -1905,6 +1978,9 @@ function taskProgress(task) {
1905
1978
  if (task.toolCalls !== undefined) {
1906
1979
  progress.push(`${task.toolCalls} call${task.toolCalls === 1 ? "" : "s"}`);
1907
1980
  }
1981
+ const usage = formatTaskUsageSummary(task.usage);
1982
+ if (usage)
1983
+ progress.push(usage);
1908
1984
  const duration = taskDuration(taskDetails(task));
1909
1985
  if (duration)
1910
1986
  progress.push(duration);
@@ -2256,8 +2332,10 @@ function parseControlResponse(value) {
2256
2332
  if (response.value !== undefined && typeof response.value !== "string") {
2257
2333
  throw new Error("Invalid supervisor response value");
2258
2334
  }
2259
- if (response.task !== undefined && (!isRecord2(response.task) || typeof response.task.taskId !== "string" || typeof response.task.state !== "string" || !TASK_STATES.has(response.task.state) || typeof response.task.agent !== "string")) {
2260
- throw new Error("Invalid supervisor task response");
2335
+ if (response.task !== undefined) {
2336
+ if (!isRecord2(response.task) || typeof response.task.taskId !== "string" || typeof response.task.state !== "string" || !TASK_STATES.has(response.task.state) || typeof response.task.agent !== "string" || response.task.usage !== undefined && !isTaskUsage(response.task.usage)) {
2337
+ throw new Error("Invalid supervisor task response");
2338
+ }
2261
2339
  }
2262
2340
  return response;
2263
2341
  }
@@ -2675,11 +2753,13 @@ ${this.primaryAgent.prompt}` };
2675
2753
  task.toolCalls === undefined ? undefined : `${task.toolCalls} tool call${task.toolCalls === 1 ? "" : "s"}`,
2676
2754
  duration
2677
2755
  ].filter(Boolean);
2756
+ const usage = formatTaskUsage(task.usage);
2678
2757
  const lines = [
2679
2758
  tabs,
2680
2759
  "",
2681
2760
  `${theme.fg("accent", theme.bold(`@${task.agent}`))} ${theme.fg("text", task.description)}`,
2682
2761
  `${taskState(theme, task)} ${theme.fg("dim", metrics.join(" · "))} ${theme.fg("dim", "·")} ${theme.fg(follow ? "accent" : "muted", `Follow: ${follow ? "on" : "off"}`)}`,
2762
+ ...usage ? [theme.fg("dim", usage)] : [],
2683
2763
  "",
2684
2764
  ...shown.map((line) => theme.fg("toolOutput", line))
2685
2765
  ];
@@ -3086,6 +3166,9 @@ ${descriptions}` : ""),
3086
3166
  const duration = taskDuration(details);
3087
3167
  if (duration)
3088
3168
  metrics.push(duration);
3169
+ const usage = formatTaskUsage(details.usage);
3170
+ if (usage)
3171
+ metrics.push(usage);
3089
3172
  if (metrics.length > 0)
3090
3173
  text += `
3091
3174
  ${theme.fg("dim", metrics.join(" · "))}`;
@@ -3334,6 +3417,15 @@ ${catalog.diagnostics.join(`
3334
3417
  update(streamedText);
3335
3418
  }
3336
3419
  }
3420
+ if (event.type === "message_end") {
3421
+ const usage = taskUsageFromMessage(event.message);
3422
+ if (usage) {
3423
+ task.usage = addTaskUsage(task.usage, usage);
3424
+ this.persist(task);
3425
+ this.updateTaskWidget(ctx);
3426
+ update(streamedText || "Subagent running");
3427
+ }
3428
+ }
3337
3429
  if (event.type === "tool_execution_start" && typeof event.toolName === "string") {
3338
3430
  task.currentTool = event.toolName;
3339
3431
  task.toolCalls = (task.toolCalls ?? 0) + 1;
@@ -3750,11 +3842,15 @@ ${renderTaskResult(task.id, "error", message)}`);
3750
3842
  render: (width) => {
3751
3843
  const header = theme.fg("accent", theme.bold("Subagents")) + theme.fg("dim", ` ${summary}`);
3752
3844
  const tree = taskTreeLines(visible, (task) => {
3845
+ const state = taskState(theme, task);
3753
3846
  const agent = theme.fg("accent", `@${task.agent}`);
3754
- const description = theme.fg("text", task.description);
3755
- const progress = taskProgress(byId.get(task.id));
3847
+ const record = byId.get(task.id);
3848
+ const progress = taskProgress(record);
3756
3849
  const suffix = progress.length > 0 ? theme.fg("dim", ` · ${progress.join(" · ")}`) : "";
3757
- return `${taskState(theme, task)} ${agent} ${description}${suffix}`;
3850
+ const indentWidth = 3 + Math.max(0, record.depth) * 3;
3851
+ const descriptionWidth = Math.max(1, width - indentWidth - visibleWidth2(`${state} ${agent} ${suffix}`));
3852
+ const description = theme.fg("text", truncateToWidth2(task.description, descriptionWidth));
3853
+ return `${state} ${agent} ${description}${suffix}`;
3758
3854
  });
3759
3855
  const shown = tree.slice(0, 8);
3760
3856
  if (tree.length > shown.length) {
@@ -3775,6 +3871,7 @@ ${renderTaskResult(task.id, "error", message)}`);
3775
3871
  continue;
3776
3872
  this.tasks.set(task.id, {
3777
3873
  ...task,
3874
+ usage: isTaskUsage(task.usage) ? { ...task.usage } : undefined,
3778
3875
  state: task.state === "running" || task.state === "queued" ? "interrupted" : task.state
3779
3876
  });
3780
3877
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-landstrip",
3
- "version": "0.18.9",
3
+ "version": "0.18.10",
4
4
  "description": "Sandboxed Bash and process-backed agents for Pi",
5
5
  "keywords": [
6
6
  "landstrip",
@@ -39,7 +39,7 @@
39
39
  "ci:test": "npm run test"
40
40
  },
41
41
  "dependencies": {
42
- "@landstrip/landstrip": "^0.18.9",
42
+ "@landstrip/landstrip": "^0.18.10",
43
43
  "jsonc-parser": "^3.3.1",
44
44
  "minimatch": "^10.2.5",
45
45
  "yaml": "^2.9.0"