@herdingchats/friday 0.1.688 → 0.1.707

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 (3) hide show
  1. package/herd.mjs +433 -263
  2. package/package.json +2 -2
  3. package/terminal-host.mjs +12 -2
package/herd.mjs CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var define_HERD_PACKAGE_default;
19
19
  var init_define_HERD_PACKAGE = __esm({
20
20
  "<define:HERD_PACKAGE>"() {
21
- define_HERD_PACKAGE_default = { version: "0.1.688", gitHead: "a284c931b34fdaa76115c8d2c91da8b2903d19ff" };
21
+ define_HERD_PACKAGE_default = { version: "0.1.707", gitHead: "092263ddfd4398fa1093a095995b57b617e624d3" };
22
22
  }
23
23
  });
24
24
 
@@ -508,7 +508,13 @@ var init_terminals = __esm({
508
508
  rows: z6.number().int().min(5).max(1e3)
509
509
  };
510
510
  terminalFrameSchema = z6.discriminatedUnion("type", [
511
- z6.object({ type: z6.literal("snapshot"), data: z6.string() }),
511
+ // The screen, and its size, which a watcher draws it at. Hosts from before sizes don't say.
512
+ z6.object({
513
+ type: z6.literal("snapshot"),
514
+ data: z6.string(),
515
+ cols: size.cols.optional(),
516
+ rows: size.rows.optional()
517
+ }),
512
518
  z6.object({ type: z6.literal("data"), data: z6.string() }),
513
519
  z6.object({ type: z6.literal("exit"), exitCode: z6.number().int().nullable() }),
514
520
  z6.object({ type: z6.literal("mode"), mode: terminalModeSchema }),
@@ -1507,7 +1513,10 @@ var init_events = __esm({
1507
1513
  kind: z13.literal("bound"),
1508
1514
  machine: z13.object({ name: nonEmpty6, online: z13.boolean() }),
1509
1515
  // The worker's name, such as `mac-mini/claude`, and never its configuration or capabilities.
1510
- worker: nonEmpty6.optional()
1516
+ worker: nonEmpty6.optional(),
1517
+ // Only for the machine's owner, while it's online: what they may do with its terminal from
1518
+ // the web. Absent for anyone else, and where the machine allows nothing or can't.
1519
+ terminal: z13.enum(["drive", "watch"]).optional()
1511
1520
  }),
1512
1521
  z13.object({ kind: z13.literal("waiting_for_machine") }),
1513
1522
  z13.object({ kind: z13.literal("author_not_linked") })
@@ -1962,8 +1971,138 @@ var init_builds = __esm({
1962
1971
  }
1963
1972
  });
1964
1973
 
1965
- // ../common-chat/src/setup.ts
1974
+ // ../common-chat/src/gateway-spend.ts
1966
1975
  import { z as z17 } from "zod";
1976
+ var spendLimitDimensionSchema, spendLimitRuleSchema, gatewaySettingsSchema, accountCreditsSchema, gatewayAnalyticsGroupSchema, gatewayAnalyticsResponseSchema, GATEWAY_SPEND_CACHE_MS, GATEWAY_SPEND_KEEP_MS, usd, modelSpendSchema, spendPeriodSchema, spendLimitScopeSchema, spendLimitSchema, creditBalanceSchema, gatewaySpendFiguresSchema, gatewaySpendResponseSchema;
1977
+ var init_gateway_spend = __esm({
1978
+ "../common-chat/src/gateway-spend.ts"() {
1979
+ "use strict";
1980
+ init_define_HERD_PACKAGE();
1981
+ spendLimitDimensionSchema = z17.discriminatedUnion("mode", [
1982
+ z17.object({ mode: z17.literal("partition") }),
1983
+ z17.object({ mode: z17.literal("filter"), values: z17.array(z17.string()) })
1984
+ ]);
1985
+ spendLimitRuleSchema = z17.object({
1986
+ id: z17.string().min(1).optional(),
1987
+ enabled: z17.boolean().optional(),
1988
+ limit: z17.number().positive(),
1989
+ limitType: z17.string(),
1990
+ window: z17.number().positive(),
1991
+ technique: z17.enum(["fixed", "sliding"]).optional(),
1992
+ metadata: z17.record(z17.string(), spendLimitDimensionSchema).nullish(),
1993
+ model: spendLimitDimensionSchema.nullish(),
1994
+ provider: spendLimitDimensionSchema.nullish()
1995
+ });
1996
+ gatewaySettingsSchema = z17.object({
1997
+ id: z17.string(),
1998
+ spend_limits: z17.object({
1999
+ enabled: z17.boolean().optional(),
2000
+ rules: z17.array(spendLimitRuleSchema).nullish()
2001
+ }).nullish()
2002
+ });
2003
+ accountCreditsSchema = z17.object({
2004
+ confirmed_balance_cents: z17.number().nullish(),
2005
+ currency: z17.string().nullish(),
2006
+ days_remaining: z17.number().nullish(),
2007
+ eligible: z17.boolean().nullish(),
2008
+ has_record: z17.boolean().nullish(),
2009
+ original_amount_cents: z17.number().nullish(),
2010
+ percent_consumed: z17.number().nullish(),
2011
+ projected_depletion_date: z17.string().nullish(),
2012
+ valid_from: z17.string().nullish(),
2013
+ valid_to: z17.string().nullish()
2014
+ });
2015
+ gatewayAnalyticsGroupSchema = z17.object({
2016
+ count: z17.number().int().nonnegative(),
2017
+ sum: z17.object({ cost: z17.number().nullish() }).nullish(),
2018
+ dimensions: z17.object({ model: z17.string().nullish(), provider: z17.string().nullish() }).nullish()
2019
+ });
2020
+ gatewayAnalyticsResponseSchema = z17.object({
2021
+ data: z17.object({
2022
+ viewer: z17.object({
2023
+ accounts: z17.array(z17.record(z17.string(), z17.array(gatewayAnalyticsGroupSchema)))
2024
+ })
2025
+ }).nullish(),
2026
+ errors: z17.array(z17.object({ message: z17.string() })).nullish()
2027
+ });
2028
+ GATEWAY_SPEND_CACHE_MS = 5 * 60 * 1e3;
2029
+ GATEWAY_SPEND_KEEP_MS = 24 * 60 * 60 * 1e3;
2030
+ usd = z17.number().nonnegative();
2031
+ modelSpendSchema = z17.object({
2032
+ model: z17.string(),
2033
+ provider: z17.string().nullable(),
2034
+ costUsd: usd,
2035
+ requests: z17.number().int().nonnegative()
2036
+ });
2037
+ spendPeriodSchema = z17.object({
2038
+ since: z17.number().int(),
2039
+ costUsd: usd,
2040
+ requests: z17.number().int().nonnegative(),
2041
+ byModel: z17.array(modelSpendSchema)
2042
+ });
2043
+ spendLimitScopeSchema = z17.object({
2044
+ /** Only these models, or any model. */
2045
+ models: z17.array(z17.string()),
2046
+ /** Only these providers, or any provider. */
2047
+ providers: z17.array(z17.string()),
2048
+ /** Dimensions that give each value its own budget, such as `model` or `metadata:cf.user_id`. */
2049
+ splitBy: z17.array(z17.string()),
2050
+ /** Custom metadata keys the rule filters or splits by, such as `cf.user_id`. */
2051
+ metadata: z17.array(z17.string())
2052
+ });
2053
+ spendLimitSchema = z17.object({
2054
+ id: z17.string().nullable(),
2055
+ enabled: z17.boolean(),
2056
+ limitUsd: z17.number().positive(),
2057
+ windowSeconds: z17.number().positive(),
2058
+ /** `fixed` counts from the window's start, and `rolling` over its length up to now. */
2059
+ windowKind: z17.enum(["fixed", "rolling"]),
2060
+ scope: spendLimitScopeSchema,
2061
+ /** When the counted window started, in ms. */
2062
+ windowStart: z17.number().int(),
2063
+ /** When a fixed window starts again, in ms, or null for a rolling one. */
2064
+ resetsAt: z17.number().int().nullable(),
2065
+ /**
2066
+ * The estimated spend counted against it, and that as a share of the limit, which can pass 1.
2067
+ * For a rule split by model or provider, that's its fullest bucket, which `bucket` names. Null
2068
+ * when analytics can't attribute spend to its buckets, as for a rule on custom metadata.
2069
+ */
2070
+ consumed: z17.object({ costUsd: usd, share: z17.number().nonnegative(), bucket: z17.string().nullable() }).nullable()
2071
+ });
2072
+ creditBalanceSchema = z17.object({ amount: z17.number(), currency: z17.string() });
2073
+ gatewaySpendFiguresSchema = z17.object({
2074
+ gateway: z17.string(),
2075
+ /** When Cloudflare was read, in ms. */
2076
+ readAt: z17.number().int(),
2077
+ today: spendPeriodSchema,
2078
+ month: spendPeriodSchema,
2079
+ /** Whether the gateway enforces its spend limits at all. */
2080
+ limitsEnabled: z17.boolean(),
2081
+ limits: z17.array(spendLimitSchema),
2082
+ credits: creditBalanceSchema.nullable(),
2083
+ /** Why the balance is missing, when it is. */
2084
+ creditsError: z17.string().nullable()
2085
+ });
2086
+ gatewaySpendResponseSchema = z17.discriminatedUnion("configured", [
2087
+ /** Before the read-only token is uploaded: what's missing, by binding name. */
2088
+ z17.object({ configured: z17.literal(false), missing: z17.array(z17.string()).min(1) }),
2089
+ z17.object({
2090
+ configured: z17.literal(true),
2091
+ /** Stand-in figures from `npm run dev`, never Cloudflare's. */
2092
+ local: z17.boolean(),
2093
+ /** Spend is Cloudflare's estimate from token counts and model pricing, not the bill. */
2094
+ estimated: z17.literal(true),
2095
+ /** The latest good figures, or null if no read has succeeded in the last day. */
2096
+ figures: gatewaySpendFiguresSchema.nullable(),
2097
+ /** Why the latest read failed, when it did. The figures are then the last good ones. */
2098
+ error: z17.string().nullable()
2099
+ })
2100
+ ]);
2101
+ }
2102
+ });
2103
+
2104
+ // ../common-chat/src/setup.ts
2105
+ import { z as z18 } from "zod";
1967
2106
  function actionText(action) {
1968
2107
  if (action.kind === "link") return `${action.label}: ${action.url}`;
1969
2108
  if (action.kind === "sign_in") {
@@ -1998,42 +2137,42 @@ var init_setup = __esm({
1998
2137
  init_activation();
1999
2138
  init_bot();
2000
2139
  SETUP_STEP_IDS = ["slack", "machine", "github", "repos"];
2001
- setupActionSchema = z17.discriminatedUnion("kind", [
2140
+ setupActionSchema = z18.discriminatedUnion("kind", [
2002
2141
  /** A page to open, which signs in to HerdingChats first. */
2003
- z17.object({ kind: z17.literal("link"), url: z17.string(), label: z17.string() }),
2142
+ z18.object({ kind: z18.literal("link"), url: z18.string(), label: z18.string() }),
2004
2143
  /** A command to run on the computer. */
2005
- z17.object({ kind: z17.literal("command"), command: z17.string() }),
2144
+ z18.object({ kind: z18.literal("command"), command: z18.string() }),
2006
2145
  /**
2007
2146
  * A sign-in herd starts on one of the person's machines, from Slack or the page, whose code
2008
2147
  * Friday sends to their direct message.
2009
2148
  */
2010
- z17.object({
2011
- kind: z17.literal("sign_in"),
2012
- machineId: z17.string(),
2149
+ z18.object({
2150
+ kind: z18.literal("sign_in"),
2151
+ machineId: z18.string(),
2013
2152
  tool: signInToolSchema,
2014
- label: z17.string()
2153
+ label: z18.string()
2015
2154
  })
2016
2155
  ]);
2017
- setupStepSchema = z17.object({
2018
- id: z17.enum(SETUP_STEP_IDS),
2156
+ setupStepSchema = z18.object({
2157
+ id: z18.enum(SETUP_STEP_IDS),
2019
2158
  /** `unavailable` is a step nobody can take yet, such as GitHub linking before an admin turns it on. */
2020
- state: z17.enum(["done", "todo", "unavailable"]),
2021
- optional: z17.boolean(),
2022
- title: z17.string(),
2159
+ state: z18.enum(["done", "todo", "unavailable"]),
2160
+ optional: z18.boolean(),
2161
+ title: z18.string(),
2023
2162
  /** What was found, such as the machine and whether it's online, or why it can't be done yet. */
2024
- detail: z17.string().nullable(),
2163
+ detail: z18.string().nullable(),
2025
2164
  /** What to do next. None once it's done, or while it's unavailable. */
2026
2165
  action: setupActionSchema.nullable(),
2027
2166
  /**
2028
2167
  * More that can be done, which doesn't decide whether the step is done, such as signing in a
2029
2168
  * computer's GitHub CLI. Shown whatever the step's state.
2030
2169
  */
2031
- extra: z17.array(setupActionSchema).optional()
2170
+ extra: z18.array(setupActionSchema).optional()
2032
2171
  });
2033
- setupStatusSchema = z17.object({
2034
- steps: z17.array(setupStepSchema),
2172
+ setupStatusSchema = z18.object({
2173
+ steps: z18.array(setupStepSchema),
2035
2174
  /** Whether every step that isn't optional is done, so Friday can take work. */
2036
- ready: z17.boolean()
2175
+ ready: z18.boolean()
2037
2176
  });
2038
2177
  READY_TEXT = `You're set up: mention @${BOT_NAME} in any channel it's in, or message it directly.`;
2039
2178
  READY_SUGGESTIONS_TITLE = "Some things to try:";
@@ -2068,6 +2207,7 @@ var init_src = __esm({
2068
2207
  init_content();
2069
2208
  init_conversation_state();
2070
2209
  init_events();
2210
+ init_gateway_spend();
2071
2211
  init_github();
2072
2212
  init_github_triggers();
2073
2213
  init_machines();
@@ -2485,7 +2625,7 @@ var init_widget = __esm({
2485
2625
  });
2486
2626
 
2487
2627
  // ../common-board/src/widgets/conversations.tsx
2488
- import { z as z30 } from "zod";
2628
+ import { z as z31 } from "zod";
2489
2629
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2490
2630
  function badge(item) {
2491
2631
  if (pullRequestBadge(item)) {
@@ -2529,10 +2669,10 @@ var init_conversations = __esm({
2529
2669
  conversations2 = defineWidget({
2530
2670
  kind: "conversations",
2531
2671
  describe: "Blocked, working and queued conversations, then the latest done, with what each is doing, where it came from and who holds it",
2532
- options: z30.object({
2533
- states: z30.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2534
- limit: z30.number().int().min(1).max(BOARD_ITEMS).default(4),
2535
- show: z30.array(z30.enum(DETAILS)).default([...DETAILS])
2672
+ options: z31.object({
2673
+ states: z31.array(conversationStateSchema).min(1).default(["stuck", "working", "waiting", "done"]),
2674
+ limit: z31.number().int().min(1).max(BOARD_ITEMS).default(4),
2675
+ show: z31.array(z31.enum(DETAILS)).default([...DETAILS])
2536
2676
  }),
2537
2677
  reads: ["items", "machines"],
2538
2678
  Component: ({ board, options, theme, now }) => {
@@ -2576,7 +2716,7 @@ var init_conversations = __esm({
2576
2716
  });
2577
2717
 
2578
2718
  // ../common-board/src/widgets/counts.tsx
2579
- import { z as z31 } from "zod";
2719
+ import { z as z32 } from "zod";
2580
2720
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2581
2721
  var COUNTS, LABELS2, counts;
2582
2722
  var init_counts = __esm({
@@ -2596,8 +2736,8 @@ var init_counts = __esm({
2596
2736
  counts = defineWidget({
2597
2737
  kind: "counts",
2598
2738
  describe: "How many conversations are stuck, unanswered, waiting and working, and done today",
2599
- options: z31.object({
2600
- show: z31.array(z31.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2739
+ options: z32.object({
2740
+ show: z32.array(z32.enum(COUNTS)).min(1).max(5).default([...COUNTS])
2601
2741
  }),
2602
2742
  reads: ["counts", "doneToday"],
2603
2743
  Component: ({ board, options, theme }) => {
@@ -2636,7 +2776,7 @@ var init_counts = __esm({
2636
2776
  });
2637
2777
 
2638
2778
  // ../common-board/src/widgets/header.tsx
2639
- import { z as z32 } from "zod";
2779
+ import { z as z33 } from "zod";
2640
2780
  import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
2641
2781
  function summary(board) {
2642
2782
  const parts = [
@@ -2661,9 +2801,9 @@ var init_header = __esm({
2661
2801
  header = defineWidget({
2662
2802
  kind: "header",
2663
2803
  describe: "The board's title, a line of how much is open, and when it was drawn",
2664
- options: z32.object({
2665
- title: z32.string().min(1).max(40).default(`@${BOT_NAME}`),
2666
- summary: z32.boolean().default(true)
2804
+ options: z33.object({
2805
+ title: z33.string().min(1).max(40).default(`@${BOT_NAME}`),
2806
+ summary: z33.boolean().default(true)
2667
2807
  }),
2668
2808
  reads: ["counts"],
2669
2809
  Component: ({ board, options, theme, now }) => {
@@ -2681,7 +2821,7 @@ var init_header = __esm({
2681
2821
  });
2682
2822
 
2683
2823
  // ../common-board/src/widgets/machines.tsx
2684
- import { z as z33 } from "zod";
2824
+ import { z as z34 } from "zod";
2685
2825
  import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
2686
2826
  function usageText(worker) {
2687
2827
  const usage = worker.usage;
@@ -2712,7 +2852,7 @@ var init_machines2 = __esm({
2712
2852
  machines = defineWidget({
2713
2853
  kind: "machines",
2714
2854
  describe: "One bubble per agent on an online machine, with its usage, most recently active first",
2715
- options: z33.object({ limit: z33.number().int().min(1).max(8).default(3) }),
2855
+ options: z34.object({ limit: z34.number().int().min(1).max(8).default(3) }),
2716
2856
  reads: ["machines"],
2717
2857
  Component: ({ board, options, theme }) => {
2718
2858
  const workers2 = board.machines.filter((machine) => machine.online).flatMap((machine) => machine.workers.map((worker) => ({ ...worker, machine: machine.name }))).sort((a, b) => (b.reportedAt ?? 0) - (a.reportedAt ?? 0));
@@ -2829,9 +2969,9 @@ var init_board2 = __esm({
2829
2969
  });
2830
2970
 
2831
2971
  // ../common-board/src/composition.ts
2832
- import { z as z34 } from "zod";
2972
+ import { z as z35 } from "zod";
2833
2973
  function readComposition(stored) {
2834
- const shape = z34.object({ widgets: z34.array(placedWidgetSchema) }).safeParse(stored);
2974
+ const shape = z35.object({ widgets: z35.array(placedWidgetSchema) }).safeParse(stored);
2835
2975
  if (!shape.success) return DEFAULT_COMPOSITION;
2836
2976
  const known = shape.data.widgets.filter(
2837
2977
  (widget) => WIDGETS[widget.kind]?.options.safeParse(widget.options).success
@@ -2846,11 +2986,11 @@ var init_composition = __esm({
2846
2986
  init_registry();
2847
2987
  MAX_WIDGETS = 12;
2848
2988
  MAX_COMPOSITION_BYTES = 4096;
2849
- placedWidgetSchema = z34.object({
2850
- kind: z34.string().min(1),
2851
- options: z34.record(z34.string(), z34.unknown()).default({})
2989
+ placedWidgetSchema = z35.object({
2990
+ kind: z35.string().min(1),
2991
+ options: z35.record(z35.string(), z35.unknown()).default({})
2852
2992
  });
2853
- compositionSchema = z34.object({ widgets: z34.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2993
+ compositionSchema = z35.object({ widgets: z35.array(placedWidgetSchema).max(MAX_WIDGETS) }).superRefine((composition, context2) => {
2854
2994
  if (JSON.stringify(composition).length > MAX_COMPOSITION_BYTES) {
2855
2995
  context2.addIssue({
2856
2996
  code: "custom",
@@ -3310,7 +3450,7 @@ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node
3310
3450
  import { homedir as homedir5 } from "node:os";
3311
3451
  import { dirname as dirname10, join as join16 } from "node:path";
3312
3452
  import { createInterface as createInterface2 } from "node:readline/promises";
3313
- import { z as z23 } from "zod";
3453
+ import { z as z24 } from "zod";
3314
3454
 
3315
3455
  // src/auth.ts
3316
3456
  init_define_HERD_PACKAGE();
@@ -3319,21 +3459,21 @@ import { chmodSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:
3319
3459
  import { createServer } from "node:http";
3320
3460
  import { homedir } from "node:os";
3321
3461
  import { join } from "node:path";
3322
- import { z as z18 } from "zod";
3462
+ import { z as z19 } from "zod";
3323
3463
  var herdHome = () => process.env.HERD_HOME ?? join(homedir(), ".herd");
3324
3464
  var apiUrl = () => (process.env.HERD_API_URL ?? "https://api.herdingchats.com").replace(/\/+$/, "");
3325
3465
  var isLocal = (url) => /^http:\/\/localhost:\d+\/?$/.test(url);
3326
3466
  var localSignIn = () => process.env.HERD_LOCAL_SLACK ? { "X-Herd-Local-Slack": process.env.HERD_LOCAL_SLACK } : {};
3327
3467
  var HerdError = class extends Error {
3328
3468
  };
3329
- var credentialsSchema = z18.object({
3330
- api: z18.string(),
3331
- tokenEndpoint: z18.string(),
3332
- revocationEndpoint: z18.string().optional(),
3333
- clientId: z18.string(),
3334
- accessToken: z18.string(),
3335
- accessTokenExpiresAt: z18.number(),
3336
- refreshToken: z18.string()
3469
+ var credentialsSchema = z19.object({
3470
+ api: z19.string(),
3471
+ tokenEndpoint: z19.string(),
3472
+ revocationEndpoint: z19.string().optional(),
3473
+ clientId: z19.string(),
3474
+ accessToken: z19.string(),
3475
+ accessTokenExpiresAt: z19.number(),
3476
+ refreshToken: z19.string()
3337
3477
  });
3338
3478
  var credentialsPath = () => join(herdHome(), "credentials.json");
3339
3479
  function readCredentials() {
@@ -3349,16 +3489,16 @@ function writeCredentials(credentials) {
3349
3489
  writeFileSync(credentialsPath(), JSON.stringify(credentials, null, 2), { mode: 384 });
3350
3490
  chmodSync(credentialsPath(), 384);
3351
3491
  }
3352
- var metadataSchema = z18.object({
3353
- authorization_endpoint: z18.string(),
3354
- token_endpoint: z18.string(),
3355
- registration_endpoint: z18.string(),
3356
- revocation_endpoint: z18.string().optional()
3492
+ var metadataSchema = z19.object({
3493
+ authorization_endpoint: z19.string(),
3494
+ token_endpoint: z19.string(),
3495
+ registration_endpoint: z19.string(),
3496
+ revocation_endpoint: z19.string().optional()
3357
3497
  });
3358
- var tokenSchema = z18.object({
3359
- access_token: z18.string().min(1),
3360
- refresh_token: z18.string().min(1),
3361
- expires_in: z18.number()
3498
+ var tokenSchema = z19.object({
3499
+ access_token: z19.string().min(1),
3500
+ refresh_token: z19.string().min(1),
3501
+ expires_in: z19.number()
3362
3502
  });
3363
3503
  var base64url = (bytes) => bytes.toString("base64url");
3364
3504
  async function postForm(url, fields) {
@@ -3467,7 +3607,7 @@ async function login(how, {
3467
3607
  token_endpoint_auth_method: "none"
3468
3608
  })
3469
3609
  });
3470
- const client = z18.object({ client_id: z18.string() }).safeParse(await registration2.json());
3610
+ const client = z19.object({ client_id: z19.string() }).safeParse(await registration2.json());
3471
3611
  if (!registration2.ok || !client.success) {
3472
3612
  throw new HerdError("The sign-in server refused to register this tool.");
3473
3613
  }
@@ -3705,7 +3845,7 @@ import { randomUUID } from "node:crypto";
3705
3845
  import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync as readFileSync4, realpathSync as realpathSync2, writeFileSync as writeFileSync3 } from "node:fs";
3706
3846
  import { homedir as homedir3, hostname } from "node:os";
3707
3847
  import { basename, dirname as dirname4, join as join6, resolve } from "node:path";
3708
- import { z as z19 } from "zod";
3848
+ import { z as z20 } from "zod";
3709
3849
 
3710
3850
  // src/login-shell.ts
3711
3851
  init_define_HERD_PACKAGE();
@@ -4484,13 +4624,13 @@ function hasUnpushed(path) {
4484
4624
 
4485
4625
  // src/config.ts
4486
4626
  init_self();
4487
- var repoSchema = z19.object({ name: z19.string(), path: z19.string(), remote: z19.string() });
4488
- var configSchema = z19.object({
4489
- machineId: z19.string(),
4490
- machineName: z19.string(),
4491
- repos: z19.array(repoSchema),
4627
+ var repoSchema = z20.object({ name: z20.string(), path: z20.string(), remote: z20.string() });
4628
+ var configSchema = z20.object({
4629
+ machineId: z20.string(),
4630
+ machineName: z20.string(),
4631
+ repos: z20.array(repoSchema),
4492
4632
  /** The machine's root folder, holding herd's clones and its conversations' folders. */
4493
- root: z19.string().optional()
4633
+ root: z20.string().optional()
4494
4634
  });
4495
4635
  var configPath = () => join6(herdHome(), "config.json");
4496
4636
  function save(config) {
@@ -4708,23 +4848,23 @@ init_src();
4708
4848
  import { randomBytes as randomBytes4, randomUUID as randomUUID3 } from "node:crypto";
4709
4849
  import { mkdirSync as mkdirSync6, readFileSync as readFileSync5, renameSync as renameSync2, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
4710
4850
  import { dirname as dirname5, join as join8 } from "node:path";
4711
- import { z as z22 } from "zod";
4851
+ import { z as z23 } from "zod";
4712
4852
 
4713
4853
  // src/permissions.ts
4714
4854
  init_define_HERD_PACKAGE();
4715
4855
  init_src();
4716
4856
  import { randomUUID as randomUUID2 } from "node:crypto";
4717
- import { z as z21 } from "zod";
4857
+ import { z as z22 } from "zod";
4718
4858
 
4719
4859
  // src/local-socket.ts
4720
4860
  init_define_HERD_PACKAGE();
4721
4861
  import { randomBytes as randomBytes3 } from "node:crypto";
4722
4862
  import { chmodSync as chmodSync2, existsSync as existsSync4, rmSync as rmSync4 } from "node:fs";
4723
4863
  import { connect, createServer as createServer2 } from "node:net";
4724
- import { z as z20 } from "zod";
4725
- var localRequestSchema = z20.object({
4726
- secret: z20.string().min(1),
4727
- op: z20.enum([
4864
+ import { z as z21 } from "zod";
4865
+ var localRequestSchema = z21.object({
4866
+ secret: z21.string().min(1),
4867
+ op: z21.enum([
4728
4868
  "inbox",
4729
4869
  "send",
4730
4870
  "run",
@@ -4756,32 +4896,32 @@ var localRequestSchema = z20.object({
4756
4896
  "activate"
4757
4897
  ]),
4758
4898
  // The key of the agent's start, from `HERD_START`, which `herd inbox` proves the start with.
4759
- start: z20.string().min(1).max(100).optional(),
4899
+ start: z21.string().min(1).max(100).optional(),
4760
4900
  // `herd repo`: the repo it added to the conversation's folder, for the daemon to record.
4761
- repo: z20.object({
4762
- remote: z20.string().min(1).max(500),
4763
- folder: z20.string().min(1).max(100),
4764
- base: z20.string().min(1).max(300).optional(),
4765
- fetchFailed: z20.string().max(1e3).optional()
4901
+ repo: z21.object({
4902
+ remote: z21.string().min(1).max(500),
4903
+ folder: z21.string().min(1).max(100),
4904
+ base: z21.string().min(1).max(300).optional(),
4905
+ fetchFailed: z21.string().max(1e3).optional()
4766
4906
  }).optional(),
4767
4907
  // `herd repo`: what the agent named and the folder it asked for, for the daemon to add.
4768
- repoAdd: z20.object({
4769
- asked: z20.string().min(1).max(500),
4770
- folder: z20.string().min(1).max(100).optional()
4908
+ repoAdd: z21.object({
4909
+ asked: z21.string().min(1).max(500),
4910
+ folder: z21.string().min(1).max(100).optional()
4771
4911
  }).optional(),
4772
4912
  // `herd delegate --repo`, given once for each repo the delegate works in.
4773
- repos: z20.array(z20.string().min(1).max(500)).max(10).optional(),
4913
+ repos: z21.array(z21.string().min(1).max(500)).max(10).optional(),
4774
4914
  // `herd watch`, `herd unwatch` and `herd watching`: what to do, and the pull request.
4775
- watch: z20.object({
4776
- action: z20.enum(["watch", "unwatch", "list"]),
4777
- url: z20.string().min(1).max(500).optional()
4915
+ watch: z21.object({
4916
+ action: z21.enum(["watch", "unwatch", "list"]),
4917
+ url: z21.string().min(1).max(500).optional()
4778
4918
  }).optional(),
4779
4919
  // `herd delegate`, `herd tell` and `herd handoff`: the worker or conversation they name.
4780
- worker: z20.string().min(1).max(200).optional(),
4781
- to: z20.string().min(1).max(200).optional(),
4920
+ worker: z21.string().min(1).max(200).optional(),
4921
+ to: z21.string().min(1).max(200).optional(),
4782
4922
  // `herd automations`: what to do, to which automation, and the definition or change it sends.
4783
- automation: z20.object({
4784
- action: z20.enum([
4923
+ automation: z21.object({
4924
+ action: z21.enum([
4785
4925
  "list",
4786
4926
  "show",
4787
4927
  "create",
@@ -4792,47 +4932,47 @@ var localRequestSchema = z20.object({
4792
4932
  "test",
4793
4933
  "rotate"
4794
4934
  ]),
4795
- id: z20.string().min(1).max(100).optional(),
4796
- body: z20.record(z20.string(), z20.unknown()).optional(),
4797
- waitMs: z20.number().int().min(0).max(6e5).optional()
4935
+ id: z21.string().min(1).max(100).optional(),
4936
+ body: z21.record(z21.string(), z21.unknown()).optional(),
4937
+ waitMs: z21.number().int().min(0).max(6e5).optional()
4798
4938
  }).optional(),
4799
4939
  // `herd work`: list, record or remove one, with the change the daemon checks again.
4800
- work: z20.object({
4801
- action: z20.enum(["list", "record", "remove"]),
4802
- change: z20.unknown().optional(),
4803
- url: z20.string().min(1).max(2048).optional()
4940
+ work: z21.object({
4941
+ action: z21.enum(["list", "record", "remove"]),
4942
+ change: z21.unknown().optional(),
4943
+ url: z21.string().min(1).max(2048).optional()
4804
4944
  }).optional(),
4805
4945
  // `herd conversations --about <url>`: only the owner's conversations about that URL.
4806
- about: z20.string().min(1).max(2048).optional(),
4946
+ about: z21.string().min(1).max(2048).optional(),
4807
4947
  // `herd investigate screen` or `herd investigate propose`, with the proposal the daemon checks.
4808
- action: z20.enum(["screen", "propose"]).optional(),
4809
- proposal: z20.unknown().optional(),
4948
+ action: z21.enum(["screen", "propose"]).optional(),
4949
+ proposal: z21.unknown().optional(),
4810
4950
  // Commands may name a conversation, but only their own is ever allowed.
4811
- conversationId: z20.string().optional(),
4951
+ conversationId: z21.string().optional(),
4812
4952
  // Where `conversations` is shown, which decides whether private titles are. Only the terminal's
4813
4953
  // own conversation is ever allowed, so a terminal can't pick one that shows more.
4814
- for: z20.string().optional(),
4815
- text: z20.string().optional(),
4954
+ for: z21.string().optional(),
4955
+ text: z21.string().optional(),
4816
4956
  // A whole message, from `herd send --json`, which the daemon checks again.
4817
- message: z20.unknown().optional(),
4818
- idempotencyKey: z20.string().optional(),
4819
- command: z20.array(z20.string()).optional(),
4957
+ message: z21.unknown().optional(),
4958
+ idempotencyKey: z21.string().optional(),
4959
+ command: z21.array(z21.string()).optional(),
4820
4960
  // `status --clear`.
4821
- clear: z20.boolean().optional(),
4961
+ clear: z21.boolean().optional(),
4822
4962
  // What an agent's status line reported, checked by the daemon.
4823
- usage: z20.unknown().optional(),
4963
+ usage: z21.unknown().optional(),
4824
4964
  // Which agent and session a status line ran for. Its reports count only from the session herd
4825
4965
  // started in the conversation's lead terminal.
4826
- agent: z20.enum(["claude-code", "codex", "agy"]).optional(),
4827
- sessionId: z20.string().min(1).max(200).optional(),
4966
+ agent: z21.enum(["claude-code", "codex", "agy"]).optional(),
4967
+ sessionId: z21.string().min(1).max(200).optional(),
4828
4968
  // What agy's status line says its agent is doing: `working` or `idle`.
4829
- state: z20.enum(["working", "idle"]).optional(),
4969
+ state: z21.enum(["working", "idle"]).optional(),
4830
4970
  // A permission request from Claude Code's hook, with the key it keeps across reconnects.
4831
- key: z20.string().min(1).max(200).optional(),
4832
- request: z20.unknown().optional(),
4971
+ key: z21.string().min(1).max(200).optional(),
4972
+ request: z21.unknown().optional(),
4833
4973
  // For one of Claude Code's other hooks: which event, and the JSON Claude Code gave it.
4834
- event: z20.string().optional(),
4835
- input: z20.record(z20.string(), z20.unknown()).optional()
4974
+ event: z21.string().optional(),
4975
+ input: z21.record(z21.string(), z21.unknown()).optional()
4836
4976
  });
4837
4977
  var LocalSocket = class {
4838
4978
  constructor(path, handle) {
@@ -4954,10 +5094,10 @@ function isAnswering(path) {
4954
5094
  }
4955
5095
 
4956
5096
  // src/permissions.ts
4957
- var hookRequestSchema = z21.looseObject({
4958
- tool_name: z21.string().min(1),
4959
- tool_input: z21.record(z21.string(), z21.unknown()).default({}),
4960
- permission_suggestions: z21.array(z21.unknown()).optional()
5097
+ var hookRequestSchema = z22.looseObject({
5098
+ tool_name: z22.string().min(1),
5099
+ tool_input: z22.record(z22.string(), z22.unknown()).default({}),
5100
+ permission_suggestions: z22.array(z22.unknown()).optional()
4961
5101
  });
4962
5102
  var DETAIL_LIMIT = 2800;
4963
5103
  var REPLY_MESSAGE_LIMIT = 2e3;
@@ -5407,9 +5547,9 @@ var activationTerminal = (agent) => `herd-activate-${agentShortName(agent)}`;
5407
5547
  var activationFolder = (root, agent) => join8(convosHome(root), ".activate", agentShortName(agent));
5408
5548
  var recordSchema = agentActivationSchema.extend({
5409
5549
  /** While it's waiting: the terminal it runs in, which `friday start` can attach to. */
5410
- terminal: z22.string().optional()
5550
+ terminal: z23.string().optional()
5411
5551
  });
5412
- var storeSchema = z22.object({ agents: z22.record(z22.string(), recordSchema) });
5552
+ var storeSchema = z23.object({ agents: z23.record(z23.string(), recordSchema) });
5413
5553
  var activationsPath = (home = herdHome()) => join8(home, "activations.json");
5414
5554
  var activationRequestPath = (home = herdHome()) => join8(home, "activate-request");
5415
5555
  function readActivations(path = activationsPath()) {
@@ -5496,6 +5636,25 @@ var Activator = class {
5496
5636
  }
5497
5637
  this.request([{ name: agent, version }]);
5498
5638
  }
5639
+ /**
5640
+ * The terminal an agent's activation runs in, for its owner to sign in from the web. One that
5641
+ * isn't running is started, and its terminal named once it's open. Null if it didn't open.
5642
+ */
5643
+ async terminalFor(agent, version, { waitMs = 6e4, pollMs = 200 } = {}) {
5644
+ if (this.current?.agent !== agent) this.request([{ name: agent, version }], { force: true });
5645
+ const until = Date.now() + waitMs;
5646
+ while (Date.now() < until && !this.stopped) {
5647
+ const current = this.current;
5648
+ if (current?.agent === agent) {
5649
+ const open = (await this.options.terminals.list().catch(() => [])).some(
5650
+ (t) => t.name === current.terminal && !t.exited
5651
+ );
5652
+ if (open) return current.terminal;
5653
+ }
5654
+ await new Promise((resolve8) => setTimeout(resolve8, pollMs));
5655
+ }
5656
+ return null;
5657
+ }
5499
5658
  /** Resolves once nothing is queued or running, as tests wait for. */
5500
5659
  async settled() {
5501
5660
  while (this.running) await this.running;
@@ -5596,7 +5755,8 @@ var Activator = class {
5596
5755
  async waitForCheckIn(version, driver, current, folder, wasSignedOut) {
5597
5756
  const { terminals } = this.options;
5598
5757
  const { agent } = current;
5599
- const deadline = Date.now() + (this.options.deadlineMs ?? ACTIVATION_DEADLINE_MS);
5758
+ const deadlineMs = this.options.deadlineMs ?? ACTIVATION_DEADLINE_MS;
5759
+ let deadline = Date.now() + deadlineMs;
5600
5760
  const restartMs = this.options.signInRestartMs ?? SIGN_IN_RESTART_MS;
5601
5761
  const checkInMs = this.options.checkInMs ?? CHECK_IN_MS;
5602
5762
  const pollMs = this.options.pollMs ?? ACTIVATION_POLL_MS;
@@ -5632,6 +5792,8 @@ var Activator = class {
5632
5792
  };
5633
5793
  await start();
5634
5794
  while (!current.checkedIn && Date.now() < deadline && !this.stopped) {
5795
+ const lookers = lookingAt((await terminals.list()).find((t) => t.name === current.terminal));
5796
+ if (lookers > 0) deadline = Math.max(deadline, Date.now() + deadlineMs);
5635
5797
  const reading = await driver.read(current.terminal).catch(() => null);
5636
5798
  if (reading?.state === "signed_out" && Date.now() - startedAt >= checkInMs) {
5637
5799
  seen = "signed_out";
@@ -7147,7 +7309,7 @@ function defaults() {
7147
7309
  print: (line2) => console.log(line2)
7148
7310
  };
7149
7311
  }
7150
- var releaseSchema = z23.object({ version: z23.string().min(1), integrity: z23.string().min(1) });
7312
+ var releaseSchema = z24.object({ version: z24.string().min(1), integrity: z24.string().min(1) });
7151
7313
  async function fetchRelease() {
7152
7314
  const response = await apiFetch("/api/daemon/release");
7153
7315
  if (response.status === 404) return null;
@@ -7788,11 +7950,11 @@ init_define_HERD_PACKAGE();
7788
7950
  // src/investigator.ts
7789
7951
  init_define_HERD_PACKAGE();
7790
7952
  init_src();
7791
- import { z as z24 } from "zod";
7953
+ import { z as z25 } from "zod";
7792
7954
  var INVESTIGATE_KEYS = ["escape", "up", "down", "enter", "tab"];
7793
- var proposalSchema = z24.object({
7794
- key: z24.enum([...INVESTIGATE_KEYS, "none"]),
7795
- reason: z24.string().trim().min(1).max(300)
7955
+ var proposalSchema = z25.object({
7956
+ key: z25.enum([...INVESTIGATE_KEYS, "none"]),
7957
+ reason: z25.string().trim().min(1).max(300)
7796
7958
  });
7797
7959
  var KEY_LABELS = {
7798
7960
  escape: "Esc",
@@ -8202,12 +8364,12 @@ init_define_HERD_PACKAGE();
8202
8364
  init_src();
8203
8365
  import { chmodSync as chmodSync3, existsSync as existsSync14, mkdirSync as mkdirSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "node:fs";
8204
8366
  import { join as join19 } from "node:path";
8205
- import { z as z25 } from "zod";
8367
+ import { z as z26 } from "zod";
8206
8368
  var BUILDS_URL = "https://builds.herdingchats.com";
8207
- var runnerKeySchema = z25.object({
8208
- runnerId: z25.string().min(1),
8209
- key: z25.string().min(20),
8210
- url: z25.string().url()
8369
+ var runnerKeySchema = z26.object({
8370
+ runnerId: z26.string().min(1),
8371
+ key: z26.string().min(20),
8372
+ url: z26.string().url()
8211
8373
  });
8212
8374
  var runnerKeyPath = () => join19(herdHome(), "build-runner.json");
8213
8375
  function readRunnerKey() {
@@ -8276,33 +8438,33 @@ async function registerRunner({
8276
8438
 
8277
8439
  // src/build-speed.ts
8278
8440
  init_define_HERD_PACKAGE();
8279
- import { z as z26 } from "zod";
8280
- var ms = z26.number().int().nullable();
8281
- var figuresSchema = z26.object({ count: z26.number().int(), median: ms, p90: ms });
8282
- var buildTimesSchema = z26.object({ run: figuresSchema, push: figuresSchema });
8283
- var stepFiguresSchema = z26.object({
8284
- ran: z26.number().int(),
8285
- remembered: z26.number().int(),
8441
+ import { z as z27 } from "zod";
8442
+ var ms = z27.number().int().nullable();
8443
+ var figuresSchema = z27.object({ count: z27.number().int(), median: ms, p90: ms });
8444
+ var buildTimesSchema = z27.object({ run: figuresSchema, push: figuresSchema });
8445
+ var stepFiguresSchema = z27.object({
8446
+ ran: z27.number().int(),
8447
+ remembered: z27.number().int(),
8286
8448
  median: ms,
8287
8449
  p90: ms,
8288
- overBudget: z26.number().int()
8450
+ overBudget: z27.number().int()
8289
8451
  });
8290
- var kindSchema = z26.object({
8291
- builds: z26.object({ current: buildTimesSchema, previous: buildTimesSchema }),
8292
- steps: z26.array(
8293
- z26.object({ label: z26.string(), current: stepFiguresSchema, previous: stepFiguresSchema })
8452
+ var kindSchema = z27.object({
8453
+ builds: z27.object({ current: buildTimesSchema, previous: buildTimesSchema }),
8454
+ steps: z27.array(
8455
+ z27.object({ label: z27.string(), current: stepFiguresSchema, previous: stepFiguresSchema })
8294
8456
  ),
8295
- overBudget: z26.array(
8296
- z26.object({ label: z26.string(), current: z26.number().int(), previous: z26.number().int() })
8457
+ overBudget: z27.array(
8458
+ z27.object({ label: z27.string(), current: z27.number().int(), previous: z27.number().int() })
8297
8459
  )
8298
8460
  });
8299
- var range = z26.object({ from: z26.number(), to: z26.number() });
8300
- var speedReportSchema = z26.object({
8301
- periods: z26.object({ current: range, previous: range }),
8302
- repos: z26.array(
8303
- z26.object({
8304
- repo: z26.string(),
8305
- capped: z26.boolean(),
8461
+ var range = z27.object({ from: z27.number(), to: z27.number() });
8462
+ var speedReportSchema = z27.object({
8463
+ periods: z27.object({ current: range, previous: range }),
8464
+ repos: z27.array(
8465
+ z27.object({
8466
+ repo: z27.string(),
8467
+ capped: z27.boolean(),
8306
8468
  pullRequests: kindSchema,
8307
8469
  main: kindSchema
8308
8470
  })
@@ -11068,9 +11230,13 @@ var Session = class {
11068
11230
  });
11069
11231
  }
11070
11232
  fromHost(message) {
11071
- if (message.type === "snapshot") {
11233
+ const size2 = typeof message.cols === "number" && typeof message.rows === "number" ? { cols: message.cols, rows: message.rows } : null;
11234
+ if (message.type === "resize" && size2) {
11235
+ this.flush();
11236
+ this.send({ type: "resize", ...size2 });
11237
+ } else if (message.type === "snapshot") {
11072
11238
  this.pending = "";
11073
- this.send({ type: "snapshot", data: message.data ?? "" });
11239
+ this.send({ type: "snapshot", data: message.data ?? "", ...size2 ?? {} });
11074
11240
  } else if (message.type === "data") {
11075
11241
  if (this.behind) return;
11076
11242
  this.pending += message.data ?? "";
@@ -11240,7 +11406,7 @@ init_define_HERD_PACKAGE();
11240
11406
  init_src();
11241
11407
  init_self();
11242
11408
  import { join as join28 } from "node:path";
11243
- import { z as z27 } from "zod";
11409
+ import { z as z28 } from "zod";
11244
11410
  function hasThread(events) {
11245
11411
  return events.some(
11246
11412
  (event2) => event2.type === "prompt.opened" || event2.type === "message.created" && !(event2.payload.direction === "inbound" && event2.payload.automation) && !event2.payload.quiet && !event2.payload.post
@@ -11306,16 +11472,16 @@ var WAITING_NOTIFICATIONS = /* @__PURE__ */ new Set([
11306
11472
  "agent_needs_input",
11307
11473
  "elicitation_dialog"
11308
11474
  ]);
11309
- var agentMessageSchema = z27.object({
11310
- text: z27.string().trim().min(1).max(12e3),
11311
- files: z27.array(z27.object({ path: z27.string().min(1), title: z27.string().optional() })).max(10).optional()
11475
+ var agentMessageSchema = z28.object({
11476
+ text: z28.string().trim().min(1).max(12e3),
11477
+ files: z28.array(z28.object({ path: z28.string().min(1), title: z28.string().optional() })).max(10).optional()
11312
11478
  });
11313
- var workspaceRepoSchema = z27.object({
11314
- folder: z27.string(),
11315
- remote: z27.string(),
11316
- branch: z27.string(),
11479
+ var workspaceRepoSchema = z28.object({
11480
+ folder: z28.string(),
11481
+ remote: z28.string(),
11482
+ branch: z28.string(),
11317
11483
  /** For a conversation from before folders: the name of the repo it was allowed as. */
11318
- name: z27.string().optional()
11484
+ name: z28.string().optional()
11319
11485
  });
11320
11486
  function fromBeforeFolders(raw) {
11321
11487
  if (!raw || typeof raw !== "object" || "folder" in raw) return raw;
@@ -11336,106 +11502,106 @@ function fromBeforeFolders(raw) {
11336
11502
  legacy: true
11337
11503
  };
11338
11504
  }
11339
- var workspaceFields = z27.object({
11340
- conversationId: z27.string(),
11505
+ var workspaceFields = z28.object({
11506
+ conversationId: z28.string(),
11341
11507
  /** The conversation's own folder, where its terminals start. */
11342
- folder: z27.string(),
11508
+ folder: z28.string(),
11343
11509
  /** The repos in it, in the order they were added. */
11344
- worktrees: z27.array(workspaceRepoSchema).default([]),
11510
+ worktrees: z28.array(workspaceRepoSchema).default([]),
11345
11511
  /** A conversation from before folders, which works in its one repo's worktree. */
11346
- legacy: z27.boolean().default(false),
11347
- lead: z27.string(),
11348
- secret: z27.string(),
11349
- readSeq: z27.number(),
11350
- claimId: z27.string().nullable(),
11351
- claimRenewedAt: z27.number(),
11352
- started: z27.boolean(),
11512
+ legacy: z28.boolean().default(false),
11513
+ lead: z28.string(),
11514
+ secret: z28.string(),
11515
+ readSeq: z28.number(),
11516
+ claimId: z28.string().nullable(),
11517
+ claimRenewedAt: z28.number(),
11518
+ started: z28.boolean(),
11353
11519
  /** Messages to deliver once the current turn ends. */
11354
- deferred: z27.boolean(),
11520
+ deferred: z28.boolean(),
11355
11521
  /** A `herd run` command finished, which the agent hears about even with nothing to claim. */
11356
- terminalNotice: z27.boolean().default(false),
11357
- status: z27.string().nullable(),
11522
+ terminalNotice: z28.boolean().default(false),
11523
+ status: z28.string().nullable(),
11358
11524
  /** Set once the thread has been told the agent is waiting, so it's told once per wait. */
11359
- toldWaiting: z27.boolean(),
11360
- runs: z27.array(z27.object({ name: z27.string(), secret: z27.string(), reported: z27.boolean() })),
11525
+ toldWaiting: z28.boolean(),
11526
+ runs: z28.array(z28.object({ name: z28.string(), secret: z28.string(), reported: z28.boolean() })),
11361
11527
  /** The subscription usage Claude Code last reported in this worktree. */
11362
11528
  usage: usageSchema.nullable().default(null),
11363
11529
  /** When that usage was reported, or 0 if it was before this was kept. */
11364
- usageAt: z27.number().default(0),
11530
+ usageAt: z28.number().default(0),
11365
11531
  /** The lead agent's Claude Code session, as its `SessionStart` hook reported it. */
11366
- sessionId: z27.string().nullable().default(null),
11532
+ sessionId: z28.string().nullable().default(null),
11367
11533
  /** When the agent's last turn ended with nothing left to do, for shutting it down later. */
11368
- idleSince: z27.number().nullable().default(null),
11534
+ idleSince: z28.number().nullable().default(null),
11369
11535
  /** The agent ran `herd exit`, so its session ends after this turn. */
11370
- exitRequested: z27.boolean().default(false),
11536
+ exitRequested: z28.boolean().default(false),
11371
11537
  /** Whether the agent sent anything with `herd send` this turn. */
11372
- sentThisTurn: z27.boolean().default(false),
11538
+ sentThisTurn: z28.boolean().default(false),
11373
11539
  /** Whether it's been asked this turn to reply. */
11374
- askedToReply: z27.boolean().default(false),
11540
+ askedToReply: z28.boolean().default(false),
11375
11541
  /** Whether it ran `herd handled` this turn, which handles the turn's messages with no reply. */
11376
- handledThisTurn: z27.boolean().default(false),
11542
+ handledThisTurn: z28.boolean().default(false),
11377
11543
  /** The newest waiting message it's been pointed at, so one arrival is pointed out once. */
11378
- nudgedSeq: z27.number().default(0),
11544
+ nudgedSeq: z28.number().default(0),
11379
11545
  /** When a `Stop` hook last ended a turn. */
11380
- hookIdleAt: z27.number().default(0),
11546
+ hookIdleAt: z28.number().default(0),
11381
11547
  /** When a turn last ended with background work still running, which keeps its messages held. */
11382
- pausedAt: z27.number().nullable().default(null),
11548
+ pausedAt: z28.number().nullable().default(null),
11383
11549
  /** The version of the terminal host the lead terminal was opened in, if it was recorded. */
11384
- hostVersion: z27.string().nullable().default(null),
11550
+ hostVersion: z28.string().nullable().default(null),
11385
11551
  /** When a notice last failed for a reason other than someone's draft, to wait before retrying. */
11386
- noticeFailedAt: z27.number().nullable().default(null),
11552
+ noticeFailedAt: z28.number().nullable().default(null),
11387
11553
  /** Why the last notice failed, so the history records each reason once, not every attempt. */
11388
- noticeFailure: z27.string().nullable().default(null),
11554
+ noticeFailure: z28.string().nullable().default(null),
11389
11555
  /** A recorded dialog herd pressed its way out of, so a dialog that stays isn't pressed again. */
11390
- closedDialog: z27.string().nullable().default(null),
11556
+ closedDialog: z28.string().nullable().default(null),
11391
11557
  /**
11392
11558
  * Prompts Claude Code's hooks reported that haven't had their `Stop`. A turn is open while this
11393
11559
  * is above zero. It's a count because a prompt queued mid-turn is reported as it's queued, and
11394
11560
  * runs as a turn of its own, ending with a second `Stop`.
11395
11561
  */
11396
- promptsOpen: z27.number().default(0),
11562
+ promptsOpen: z28.number().default(0),
11397
11563
  /** The messages the latest claim or addition took, for choosing which notice to type. */
11398
- claimedSeqs: z27.array(z27.number()).default([]),
11564
+ claimedSeqs: z28.array(z28.number()).default([]),
11399
11565
  /** The investigator looking at a screen herd can't read, from opening to the owner's answer. */
11400
- investigation: z27.object({
11401
- terminal: z27.string(),
11402
- secret: z27.string(),
11403
- startedAt: z27.number(),
11566
+ investigation: z28.object({
11567
+ terminal: z28.string(),
11568
+ secret: z28.string(),
11569
+ startedAt: z28.number(),
11404
11570
  /** The stuck screen when it opened, saved with the key if the owner approves one. */
11405
- screen: z27.array(z27.string()),
11406
- key: z27.string().nullable(),
11407
- promptId: z27.string().nullable()
11571
+ screen: z28.array(z28.string()),
11572
+ key: z28.string().nullable(),
11573
+ promptId: z28.string().nullable()
11408
11574
  }).nullable().default(null),
11409
11575
  /** The failed notice an investigator was opened for, so each failure gets one. */
11410
- investigatedFailure: z27.number().nullable().default(null),
11576
+ investigatedFailure: z28.number().nullable().default(null),
11411
11577
  /** The agent CLI this conversation's worker runs, as its offer named it. */
11412
- agent: z27.string().default("claude-code"),
11578
+ agent: z28.string().default("claude-code"),
11413
11579
  /** Whether what the current claim took needs a reply, which other agents' messages don't. */
11414
- replyWanted: z27.boolean().default(true),
11580
+ replyWanted: z28.boolean().default(true),
11415
11581
  /** For a delegate: the conversation that started it, which its starting prompt names. */
11416
- parent: z27.string().nullable().default(null),
11582
+ parent: z28.string().nullable().default(null),
11417
11583
  /** Codex's session file for this worktree, once found, and how far the daemon has read it. */
11418
- codexFile: z27.string().nullable().default(null),
11419
- codexOffset: z27.number().default(0),
11584
+ codexFile: z28.string().nullable().default(null),
11585
+ codexOffset: z28.number().default(0),
11420
11586
  /** When the agent was last started, so Codex's new session file is told from older ones. */
11421
- agentStartedAt: z27.number().default(0),
11587
+ agentStartedAt: z28.number().default(0),
11422
11588
  /**
11423
11589
  * The key the agent was last started with, as `HERD_START`. Its `herd inbox` carrying this key
11424
11590
  * is what proves the start works: never a hook, a prompt being taken, or the screen.
11425
11591
  */
11426
- startKey: z27.string().nullable().default(null),
11592
+ startKey: z28.string().nullable().default(null),
11427
11593
  /** Whether the current start has checked in. One from before keys counts as proven. */
11428
- startProven: z27.boolean().default(true),
11594
+ startProven: z28.boolean().default(true),
11429
11595
  /** When the check-in window of an unproven start began, or null once it's proven or for none. */
11430
- checkInFrom: z27.number().nullable().default(null),
11596
+ checkInFrom: z28.number().nullable().default(null),
11431
11597
  /** Whether this start has been flagged as needing someone at its terminal, so it's said once. */
11432
- needsAttention: z27.boolean().default(false),
11598
+ needsAttention: z28.boolean().default(false),
11433
11599
  /** What agy's status line last said its agent was doing, and when. */
11434
- agentState: z27.enum(["working", "idle"]).nullable().default(null),
11435
- agentStateAt: z27.number().default(0)
11600
+ agentState: z28.enum(["working", "idle"]).nullable().default(null),
11601
+ agentStateAt: z28.number().default(0)
11436
11602
  });
11437
- var workspaceSchema = z27.preprocess(fromBeforeFolders, workspaceFields);
11438
- var stateSchema = z27.object({ workspaces: z27.record(z27.string(), workspaceSchema) });
11603
+ var workspaceSchema = z28.preprocess(fromBeforeFolders, workspaceFields);
11604
+ var stateSchema = z28.object({ workspaces: z28.record(z28.string(), workspaceSchema) });
11439
11605
 
11440
11606
  // src/workspaces/claims.ts
11441
11607
  var holder = (ws, workspace) => `${ws.options.machineName()}/${workspace.lead}`;
@@ -11516,7 +11682,7 @@ import { join as join29 } from "node:path";
11516
11682
 
11517
11683
  // src/commands/repo.ts
11518
11684
  init_define_HERD_PACKAGE();
11519
- import { z as z28 } from "zod";
11685
+ import { z as z29 } from "zod";
11520
11686
  var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
11521
11687
 
11522
11688
  Checks a repo out in this conversation's folder, on a branch named for the conversation, started
@@ -11530,16 +11696,16 @@ var REPOS_HELP = `Usage: herd repos [--json]
11530
11696
 
11531
11697
  Lists the repos in this conversation's folder, the repos checked out in this machine's root, and
11532
11698
  the repos your owner's herd.config.ts describes, with what each is for. Add one with herd repo.`;
11533
- var repoContextSchema = z28.object({
11534
- folder: z28.string(),
11535
- root: z28.string(),
11536
- branch: z28.string(),
11537
- legacy: z28.boolean(),
11538
- legacyReason: z28.string().optional(),
11539
- recorded: z28.array(z28.object({ folder: z28.string(), remote: z28.string(), branch: z28.string() })),
11540
- cloned: z28.array(z28.object({ key: z28.string(), remote: z28.string() })),
11541
- described: z28.array(
11542
- z28.object({ remote: z28.string(), key: z28.string().nullable(), about: z28.string().optional() })
11699
+ var repoContextSchema = z29.object({
11700
+ folder: z29.string(),
11701
+ root: z29.string(),
11702
+ branch: z29.string(),
11703
+ legacy: z29.boolean(),
11704
+ legacyReason: z29.string().optional(),
11705
+ recorded: z29.array(z29.object({ folder: z29.string(), remote: z29.string(), branch: z29.string() })),
11706
+ cloned: z29.array(z29.object({ key: z29.string(), remote: z29.string() })),
11707
+ described: z29.array(
11708
+ z29.object({ remote: z29.string(), key: z29.string().nullable(), about: z29.string().optional() })
11543
11709
  )
11544
11710
  });
11545
11711
  var isUrl = (text4) => /^[a-z][a-z0-9+.-]*:\/\//i.test(text4) || text4.startsWith("/") || /^[^@/\s]+@[^:/\s]+:/.test(text4);
@@ -11596,7 +11762,7 @@ async function runRepo(rest) {
11596
11762
  op: "repo-add",
11597
11763
  repoAdd: { asked, ...folderName !== void 0 ? { folder: folderName } : {} }
11598
11764
  });
11599
- const lines = z28.array(z28.string()).safeParse(reply.lines);
11765
+ const lines = z29.array(z29.string()).safeParse(reply.lines);
11600
11766
  if (!lines.success) throw new HerdError("The daemon's answer about this repo wasn't readable.");
11601
11767
  console.log(lines.data.join("\n"));
11602
11768
  return 0;
@@ -12775,21 +12941,21 @@ function resolveAttachments(worktree, files) {
12775
12941
  // src/workers.ts
12776
12942
  init_define_HERD_PACKAGE();
12777
12943
  init_src();
12778
- import { z as z29 } from "zod";
12779
- var workerListingSchema = z29.object({
12780
- workers: z29.array(workerSchema),
12781
- machines: z29.array(
12782
- z29.object({
12783
- id: z29.string(),
12784
- name: z29.string(),
12785
- online: z29.boolean(),
12786
- missing: z29.array(z29.string()),
12787
- configError: z29.string().optional(),
12944
+ import { z as z30 } from "zod";
12945
+ var workerListingSchema = z30.object({
12946
+ workers: z30.array(workerSchema),
12947
+ machines: z30.array(
12948
+ z30.object({
12949
+ id: z30.string(),
12950
+ name: z30.string(),
12951
+ online: z30.boolean(),
12952
+ missing: z30.array(z30.string()),
12953
+ configError: z30.string().optional(),
12788
12954
  // Servers from before versions were reported send none.
12789
- herdVersion: z29.string().nullable().optional()
12955
+ herdVersion: z30.string().nullable().optional()
12790
12956
  })
12791
12957
  ),
12792
- configType: z29.string()
12958
+ configType: z30.string()
12793
12959
  });
12794
12960
  async function readWorkers(fetch2) {
12795
12961
  const response = await fetch2("/api/workers");
@@ -12857,20 +13023,20 @@ function formatWorkers(listing, current = null) {
12857
13023
  );
12858
13024
  return lines.join("\n");
12859
13025
  }
12860
- var routingCheckSchema = z29.object({
12861
- suits: z29.array(z29.object({ key: z29.string(), name: z29.string() })),
12862
- eligible: z29.array(z29.object({ key: z29.string(), name: z29.string() })),
12863
- removed: z29.array(z29.object({ key: z29.string(), name: z29.string(), rule: z29.string() })),
12864
- questions: z29.array(
12865
- z29.object({ id: z29.string(), question: z29.string(), probability: z29.number().nullable() })
13026
+ var routingCheckSchema = z30.object({
13027
+ suits: z30.array(z30.object({ key: z30.string(), name: z30.string() })),
13028
+ eligible: z30.array(z30.object({ key: z30.string(), name: z30.string() })),
13029
+ removed: z30.array(z30.object({ key: z30.string(), name: z30.string(), rule: z30.string() })),
13030
+ questions: z30.array(
13031
+ z30.object({ id: z30.string(), question: z30.string(), probability: z30.number().nullable() })
12866
13032
  ),
12867
- fallback: z29.string().optional(),
12868
- fits: z29.array(
12869
- z29.object({
12870
- key: z29.string(),
12871
- fit: z29.number(),
12872
- statement: z29.string().optional(),
12873
- level: z29.string().optional()
13033
+ fallback: z30.string().optional(),
13034
+ fits: z30.array(
13035
+ z30.object({
13036
+ key: z30.string(),
13037
+ fit: z30.number(),
13038
+ statement: z30.string().optional(),
13039
+ level: z30.string().optional()
12874
13040
  })
12875
13041
  ).optional()
12876
13042
  });
@@ -14254,8 +14420,12 @@ async function runDaemon(args) {
14254
14420
  }),
14255
14421
  access: () => daemon?.configuration().config?.terminals?.remote ?? "drive",
14256
14422
  resolve: async (target) => {
14257
- if (target.kind !== "conversation") {
14258
- return { refused: "This machine can't open an agent's sign-in terminal from the web yet." };
14423
+ if (target.kind === "activation") {
14424
+ const cli = clis.find((candidate) => candidate.name === target.agent);
14425
+ if (!cli || !activator) return { refused: "This machine doesn't run that agent." };
14426
+ const terminal = await activator.terminalFor(cli.name, cli.version);
14427
+ if (!terminal) return { refused: "The agent's sign-in didn't start on this machine." };
14428
+ return { terminal, socketPath: await terminals.pathOf(terminal) };
14259
14429
  }
14260
14430
  const workspace = workspaces.get(target.conversationId);
14261
14431
  if (!workspace) return { refused: "This machine doesn't hold that conversation." };
@@ -15321,7 +15491,7 @@ async function runWatch(command, rest) {
15321
15491
  // src/commands/work.ts
15322
15492
  init_define_HERD_PACKAGE();
15323
15493
  init_src();
15324
- import { z as z35 } from "zod";
15494
+ import { z as z36 } from "zod";
15325
15495
  var WORK_HELP = `Usage: herd work <url> [--kind <kind>] [--title "<title>"] [--status <status>]
15326
15496
  [--detail "<detail>"] [--made] [--phase <phase>]
15327
15497
  herd work --remove <url>
@@ -15413,9 +15583,9 @@ ${WORK_HELP}`
15413
15583
  };
15414
15584
  return { kind: "record", change, json };
15415
15585
  }
15416
- var followSchema = z35.union([
15417
- z35.object({ following: z35.literal(true) }),
15418
- z35.object({ following: z35.literal(false), reason: z35.string() })
15586
+ var followSchema = z36.union([
15587
+ z36.object({ following: z36.literal(true) }),
15588
+ z36.object({ following: z36.literal(false), reason: z36.string() })
15419
15589
  ]);
15420
15590
  function formatFollow(follow) {
15421
15591
  return follow.following ? "Following it: its reviews, CI and merge will reach herd inbox." : `Not followed: ${follow.reason}`;
@@ -15431,7 +15601,7 @@ function formatWork(work, own) {
15431
15601
  return ` ${parts.join(" \xB7 ")}${work.made ? " made here" : ""}
15432
15602
  ${work.url}${whose}`;
15433
15603
  }
15434
- var listSchema = z35.array(workObjectSchema);
15604
+ var listSchema = z36.array(workObjectSchema);
15435
15605
  async function runWork(rest) {
15436
15606
  const parsed = parseWork(rest);
15437
15607
  if (parsed.kind === "help") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herdingchats/friday",
3
- "version": "0.1.688",
3
+ "version": "0.1.707",
4
4
  "description": "Friday, HerdingChats' agent daemon, and the herd command it runs agents with.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,5 +22,5 @@
22
22
  "satori": "0.32.0",
23
23
  "zod": "4.6.7"
24
24
  },
25
- "gitHead": "a284c931b34fdaa76115c8d2c91da8b2903d19ff"
25
+ "gitHead": "092263ddfd4398fa1093a095995b57b617e624d3"
26
26
  }
package/terminal-host.mjs CHANGED
@@ -230,6 +230,16 @@ var TerminalHost = class {
230
230
  if (held.info.exited) return;
231
231
  held.pty.resize(cols, rows);
232
232
  held.term.resize(cols, rows);
233
+ for (const socket of held.watchers) {
234
+ socket.write(`${JSON.stringify({ type: "resize", cols, rows })}
235
+ `);
236
+ }
237
+ }
238
+ /** The screen as `attach` and `watch` send it first: its contents, and its size. */
239
+ async sized(name) {
240
+ const held = this.get(name);
241
+ const data = await this.snapshot(name);
242
+ return { type: "snapshot", data, cols: held.term.cols, rows: held.term.rows };
233
243
  }
234
244
  subscribe(name, socket) {
235
245
  const held = this.get(name);
@@ -253,7 +263,7 @@ var TerminalHost = class {
253
263
  return;
254
264
  }
255
265
  if (size.cols && size.rows) this.resize(name, size.cols, size.rows);
256
- socket.write(`${JSON.stringify({ type: "snapshot", data: await this.snapshot(name) })}
266
+ socket.write(`${JSON.stringify(await this.sized(name))}
257
267
  `);
258
268
  held.viewers.add(socket);
259
269
  held.drivenAt = Date.now();
@@ -294,7 +304,7 @@ var TerminalHost = class {
294
304
  `);
295
305
  return;
296
306
  }
297
- socket.write(`${JSON.stringify({ type: "snapshot", data: await this.snapshot(name) })}
307
+ socket.write(`${JSON.stringify(await this.sized(name))}
298
308
  `);
299
309
  held.watchers.add(socket);
300
310
  held.subscribers.add(socket);