@hanzo/build 0.2.79 → 0.2.81

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 (167) hide show
  1. package/README.md +22 -4
  2. package/lib/activity.d.ts +3 -7
  3. package/lib/activity.js +18 -5
  4. package/lib/api/agents.d.ts +36 -35
  5. package/lib/api/agents.js +66 -29
  6. package/lib/api/auto.d.ts +3 -23
  7. package/lib/api/auto.js +39 -22
  8. package/lib/api/billing.d.ts +0 -35
  9. package/lib/api/billing.js +6 -5
  10. package/lib/api/call.d.ts +16 -8
  11. package/lib/api/call.js +32 -1
  12. package/lib/api/capabilities.d.ts +0 -11
  13. package/lib/api/capabilities.js +2 -1
  14. package/lib/api/codebases.d.ts +0 -10
  15. package/lib/api/codebases.js +3 -2
  16. package/lib/api/coding.d.ts +3 -32
  17. package/lib/api/coding.js +13 -7
  18. package/lib/api/connectors.d.ts +1 -17
  19. package/lib/api/connectors.js +5 -4
  20. package/lib/api/environment.d.ts +6 -20
  21. package/lib/api/environment.js +22 -19
  22. package/lib/api/github.d.ts +0 -22
  23. package/lib/api/github.js +3 -2
  24. package/lib/api/keys.d.ts +0 -14
  25. package/lib/api/keys.js +4 -3
  26. package/lib/api/library.d.ts +3 -17
  27. package/lib/api/library.js +23 -22
  28. package/lib/api/machines.d.ts +0 -15
  29. package/lib/api/machines.js +6 -5
  30. package/lib/api/mcp.d.ts +0 -9
  31. package/lib/api/mcp.js +3 -2
  32. package/lib/api/members.d.ts +0 -15
  33. package/lib/api/members.js +4 -3
  34. package/lib/api/memory.d.ts +0 -11
  35. package/lib/api/memory.js +2 -1
  36. package/lib/api/places.d.ts +1 -9
  37. package/lib/api/places.js +12 -1
  38. package/lib/api/plugins.d.ts +1 -16
  39. package/lib/api/plugins.js +4 -3
  40. package/lib/api/profile.d.ts +0 -10
  41. package/lib/api/profile.js +5 -4
  42. package/lib/api/projects.d.ts +0 -14
  43. package/lib/api/projects.js +2 -1
  44. package/lib/api/provider.d.ts +2 -14
  45. package/lib/api/provider.js +12 -10
  46. package/lib/api/sandbox.d.ts +0 -17
  47. package/lib/api/sandbox.js +5 -4
  48. package/lib/api/sessions.d.ts +0 -17
  49. package/lib/api/sessions.js +5 -4
  50. package/lib/api/skills.d.ts +1 -15
  51. package/lib/api/skills.js +5 -4
  52. package/lib/api/studio.d.ts +0 -14
  53. package/lib/api/studio.js +6 -5
  54. package/lib/api/turn.d.ts +6 -28
  55. package/lib/api/turn.js +105 -49
  56. package/lib/api/verdict.d.ts +0 -9
  57. package/lib/api/verdict.js +2 -1
  58. package/lib/api/webhooks.d.ts +1 -14
  59. package/lib/api/webhooks.js +5 -4
  60. package/lib/ask.js +7 -4
  61. package/lib/automations.d.ts +5 -4
  62. package/lib/automations.js +95 -74
  63. package/lib/back.js +10 -4
  64. package/lib/builder.js +7 -5
  65. package/lib/carry.js +20 -16
  66. package/lib/customize/agents.d.ts +3 -0
  67. package/lib/customize/agents.js +152 -55
  68. package/lib/customize/connectors.js +62 -48
  69. package/lib/customize/index.js +45 -14
  70. package/lib/customize/plugins.js +34 -22
  71. package/lib/customize/say.d.ts +13 -1
  72. package/lib/customize/say.js +15 -21
  73. package/lib/customize/skills.js +31 -22
  74. package/lib/customize/ui.d.ts +5 -9
  75. package/lib/customize/ui.js +26 -16
  76. package/lib/data.d.ts +1 -1
  77. package/lib/data.js +3 -2
  78. package/lib/desk.js +45 -33
  79. package/lib/door.js +35 -10
  80. package/lib/environment.js +25 -19
  81. package/lib/files.js +43 -34
  82. package/lib/find.js +12 -9
  83. package/lib/foot.js +7 -4
  84. package/lib/forge.js +61 -40
  85. package/lib/gear.d.ts +2 -1
  86. package/lib/gear.js +6 -4
  87. package/lib/git.js +29 -19
  88. package/lib/host.d.ts +3 -1
  89. package/lib/landing.d.ts +5 -5
  90. package/lib/landing.js +52 -44
  91. package/lib/library.js +73 -51
  92. package/lib/meter.d.ts +1 -1
  93. package/lib/meter.js +14 -5
  94. package/lib/mic.js +12 -5
  95. package/lib/mind.d.ts +1 -0
  96. package/lib/mind.js +5 -3
  97. package/lib/nav.d.ts +6 -5
  98. package/lib/nav.js +37 -30
  99. package/lib/panel.d.ts +2 -1
  100. package/lib/panel.js +52 -36
  101. package/lib/plan.d.ts +4 -20
  102. package/lib/plan.js +26 -10
  103. package/lib/plans.js +49 -30
  104. package/lib/plugins/design.js +3 -2
  105. package/lib/plugins/index.d.ts +23 -4
  106. package/lib/plugins/index.js +11 -26
  107. package/lib/plugins/pdf.js +2 -1
  108. package/lib/plugins/project.js +6 -3
  109. package/lib/plugins/research.js +3 -2
  110. package/lib/plugins/sketch.js +6 -3
  111. package/lib/plugins/slides.js +3 -2
  112. package/lib/plugins/template.js +6 -3
  113. package/lib/plus.js +25 -13
  114. package/lib/prefs.d.ts +0 -14
  115. package/lib/prefs.js +2 -1
  116. package/lib/project.js +69 -62
  117. package/lib/prompt.d.ts +12 -11
  118. package/lib/prompt.js +38 -20
  119. package/lib/publish.js +17 -12
  120. package/lib/reply.js +6 -3
  121. package/lib/run.js +72 -54
  122. package/lib/say.d.ts +2 -0
  123. package/lib/say.js +7 -0
  124. package/lib/scheduled.js +54 -43
  125. package/lib/section.d.ts +1 -0
  126. package/lib/section.js +8 -2
  127. package/lib/settings/account.d.ts +1 -0
  128. package/lib/settings/account.js +31 -21
  129. package/lib/settings/billing.js +55 -28
  130. package/lib/settings/capabilities.js +26 -15
  131. package/lib/settings/card.js +19 -12
  132. package/lib/settings/code.js +22 -11
  133. package/lib/settings/credits.d.ts +4 -4
  134. package/lib/settings/credits.js +34 -21
  135. package/lib/settings/environments.js +21 -14
  136. package/lib/settings/general.js +26 -14
  137. package/lib/settings/index.js +6 -3
  138. package/lib/settings/integrations.js +42 -28
  139. package/lib/settings/keys.js +37 -19
  140. package/lib/settings/list.d.ts +0 -5
  141. package/lib/settings/list.js +24 -15
  142. package/lib/settings/machines.d.ts +3 -0
  143. package/lib/settings/machines.js +63 -38
  144. package/lib/settings/members.js +23 -14
  145. package/lib/settings/memory.js +12 -6
  146. package/lib/settings/notifications.js +32 -19
  147. package/lib/settings/plan.d.ts +1 -1
  148. package/lib/settings/plan.js +40 -17
  149. package/lib/settings/privacy.js +26 -13
  150. package/lib/settings/ui.d.ts +3 -2
  151. package/lib/settings/ui.js +9 -3
  152. package/lib/settings/usage.js +7 -3
  153. package/lib/shelf.js +18 -15
  154. package/lib/side.d.ts +1 -26
  155. package/lib/side.js +11 -5
  156. package/lib/standing.d.ts +2 -10
  157. package/lib/standing.js +5 -3
  158. package/lib/suggest.d.ts +8 -28
  159. package/lib/suggest.js +39 -4
  160. package/lib/suggestions.js +14 -5
  161. package/lib/summary.d.ts +1 -1
  162. package/lib/summary.js +4 -1
  163. package/lib/sync.js +22 -12
  164. package/lib/tools.d.ts +4 -3
  165. package/lib/tools.js +10 -7
  166. package/lib/transcript.js +10 -8
  167. package/package.json +4 -2
@@ -15,6 +15,7 @@
15
15
  * falls 256 frames behind is dropped. So `watch` reconnects, and every
16
16
  * reconnect is reported, which is the caller's cue to re-read the detail.
17
17
  */
18
+ import { phrase } from '@hanzo/i18n';
18
19
  import { call, headers, query, Refusal, seg } from './call.js';
19
20
  import { read } from './sse.js';
20
21
  const str = (v) => (typeof v === 'string' ? v : '');
@@ -93,7 +94,7 @@ export async function get(t, id) {
93
94
  export async function message(t, id, text) {
94
95
  const m = text.trim();
95
96
  if (!m)
96
- throw new Refusal(400, 'Say something to the run');
97
+ throw new Refusal(400, phrase('Say something to the run'));
97
98
  await call(t, 'POST', `/v1/agent/sessions/${seg(id)}/message`, { message: m });
98
99
  }
99
100
  export async function stop(t, id, why = 'Stopped from the builder') {
@@ -109,9 +110,9 @@ export async function resume(t, id) {
109
110
  export async function rename(t, id, title) {
110
111
  const name = title.trim();
111
112
  if (!name)
112
- throw new Refusal(400, 'Give the run a name');
113
+ throw new Refusal(400, phrase('Give the run a name'));
113
114
  if (name.length > 512)
114
- throw new Refusal(400, 'A run’s name is at most 512 characters');
115
+ throw new Refusal(400, phrase('A run’s name is at most 512 characters'));
115
116
  return session(await call(t, 'PATCH', `/v1/agent/sessions/${seg(id)}`, { title: name }));
116
117
  }
117
118
  /**
@@ -139,7 +140,7 @@ export async function watch(t, root, on, signal) {
139
140
  signal,
140
141
  });
141
142
  if (res.status === 401 || res.status === 403)
142
- throw new Refusal(res.status, 'This account cannot follow this run');
143
+ throw new Refusal(res.status, phrase('This account cannot follow this run'));
143
144
  if (!res.ok || !res.body)
144
145
  throw new Error(`stream answered ${res.status}`);
145
146
  on.open?.(opened++);
@@ -1,17 +1,3 @@
1
- /**
2
- * Skills: what an agent knows how to do, as a SKILL.md it reads.
3
- *
4
- * GET /v1/tool/skills?activated=true {tools: [Tool]} the org's skills that are on, brand and own
5
- * GET /v1/tool/skills/authored {skills: [skill]} the org's own, with their SKILL.md
6
- * POST /v1/tool/skills {name, description, content} → 201 {skill}; the same name revises it
7
- * DELETE /v1/tool/skills/{id} {deleted}
8
- * GET /.well-known/agent-skills/index.json the brand's catalogue, public
9
- * GET /.well-known/agent-skills/{name}/SKILL.md one of its documents, public
10
- *
11
- * A skill is on when its tool name, `skill_<name>`, is activated (tools.ts):
12
- * that is how a catalogue skill is added to an org and how an org's own skill is
13
- * switched off. The brand's skill wins a name an org's own skill also takes.
14
- */
15
1
  import { type Target } from './call.ts';
16
2
  import { type Tool } from './tools.ts';
17
3
  /** An org's own skill. */
@@ -56,7 +42,7 @@ export declare function catalogue(raw: unknown): Catalogue;
56
42
  /** The org's skills that are on, brand and own. */
57
43
  export declare function active(t: Target): Promise<Tool[]>;
58
44
  export declare function authored(t: Target): Promise<Skill[]>;
59
- /** Why a skill cannot be written as it stands, or '' when it can. */
45
+ /** Why a skill cannot be written as it stands, or '' when it can. English, marked (`phrase`): a screen says it through `t`. */
60
46
  export declare function refuse(s: {
61
47
  name: string;
62
48
  content: string;
package/lib/api/skills.js CHANGED
@@ -12,6 +12,7 @@
12
12
  * that is how a catalogue skill is added to an org and how an org's own skill is
13
13
  * switched off. The brand's skill wins a name an org's own skill also takes.
14
14
  */
15
+ import { phrase } from '@hanzo/i18n';
15
16
  import { call, reason, Refusal, seg } from './call.js';
16
17
  import { toolsOf } from './tools.js';
17
18
  /** A skill's name: one lowercase path segment, as the handler takes it. */
@@ -63,14 +64,14 @@ export async function active(t) {
63
64
  export async function authored(t) {
64
65
  return rows(obj(await call(t, 'GET', '/v1/tool/skills/authored')).skills).map(skill).filter((s) => s.name);
65
66
  }
66
- /** Why a skill cannot be written as it stands, or '' when it can. */
67
+ /** Why a skill cannot be written as it stands, or '' when it can. English, marked (`phrase`): a screen says it through `t`. */
67
68
  export function refuse(s) {
68
69
  if (!NAME.test(s.name))
69
- return 'A name is one lowercase word: letters, digits, _ or -';
70
+ return phrase('A name is one lowercase word: letters, digits, _ or -');
70
71
  if (!s.content.trim())
71
- return 'A skill needs its SKILL.md';
72
+ return phrase('A skill needs its SKILL.md');
72
73
  if (new TextEncoder().encode(s.content).length > MAX)
73
- return 'A SKILL.md is at most 256 KB';
74
+ return phrase('A SKILL.md is at most 256 KB');
74
75
  return '';
75
76
  }
76
77
  export async function write(t, s) {
@@ -1,17 +1,3 @@
1
- /**
2
- * Hanzo Studio: a creation is a run of a template, its files kept in the Library.
3
- *
4
- * GET /v1/studio/templates {data: [template]} the public catalog, then the org's own
5
- * POST /v1/studio/runs {template, input, title?, parent?} → 201 run, queued
6
- * GET /v1/studio/runs/{id} the run: status, text, outputs
7
- * POST /v1/studio/runs/{id}/cancel stop it; what it reserved is released
8
- *
9
- * A run is priced and its estimate reserved against the org's wallet before it
10
- * is queued (402 when the wallet cannot cover it). `parent` makes the run the
11
- * next version of that one. Its outputs are Library items: their `url` is
12
- * signed and expires, their `origin` is the app path that opens the run in
13
- * Studio and does not.
14
- */
15
1
  import { type Target } from './call.ts';
16
2
  import { type Item } from './library.ts';
17
3
  export interface Run {
package/lib/api/studio.js CHANGED
@@ -12,7 +12,8 @@
12
12
  * signed and expires, their `origin` is the app path that opens the run in
13
13
  * Studio and does not.
14
14
  */
15
- import { call, Refusal, seg } from './call.js';
15
+ import { phrase } from '@hanzo/i18n';
16
+ import { call, Refusal, seg, Sentence } from './call.js';
16
17
  import { item } from './library.js';
17
18
  export function brandOf(raw) {
18
19
  const o = obj(raw);
@@ -65,10 +66,10 @@ const pause = (ms, signal) => new Promise((done, fail) => {
65
66
  */
66
67
  export async function make(t, s, signal) {
67
68
  let r = run(await call(t, 'POST', '/v1/studio/runs', s, { signal }).catch((e) => {
68
- throw e instanceof Refusal && e.status === 402 ? new Error('Your balance does not cover it. Add credit in Settings › Billing.') : e;
69
+ throw e instanceof Refusal && e.status === 402 ? new Error(phrase('Your balance does not cover it. Add credit in Settings › Billing.')) : e;
69
70
  }));
70
71
  if (!r.id)
71
- throw new Error('Studio started the run and named no id');
72
+ throw new Error(phrase('Studio started the run and named no id'));
72
73
  const id = r.id;
73
74
  const stop = () => void call(t, 'POST', `/v1/studio/runs/${seg(id)}/cancel`, {}).catch(() => undefined);
74
75
  signal?.addEventListener('abort', stop, { once: true });
@@ -76,7 +77,7 @@ export async function make(t, s, signal) {
76
77
  const until = Date.now() + LONGEST;
77
78
  while (!ended(r)) {
78
79
  if (Date.now() > until)
79
- throw new Error('Studio is still making it. It will be in your Library when it is done.');
80
+ throw new Error(phrase('Studio is still making it. It will be in your Library when it is done.'));
80
81
  await pause(EVERY, signal);
81
82
  r = run(await call(t, 'GET', `/v1/studio/runs/${seg(id)}`, undefined, { signal }));
82
83
  }
@@ -85,6 +86,6 @@ export async function make(t, s, signal) {
85
86
  signal?.removeEventListener('abort', stop);
86
87
  }
87
88
  if (r.status !== 'succeeded')
88
- throw new Error(r.error || `Studio could not make it (${r.status})`);
89
+ throw r.error ? new Error(r.error) : new Sentence(phrase('Studio could not make it ({status})'), { status: r.status });
89
90
  return r;
90
91
  }
package/lib/api/turn.d.ts CHANGED
@@ -1,26 +1,4 @@
1
- /**
2
- * What a run's events SAY, as text a person reads.
3
- *
4
- * A curated projection, never a dump: an event carries the machinery a run
5
- * happened on — hosts, paths, whole file bodies — and printing it would put
6
- * someone's filesystem in front of anyone who can read the run. Each kind gets a
7
- * sentence built from the few fields that describe the outcome; the rest is
8
- * dropped by omission, because a projection cannot leak a field it never reads.
9
- *
10
- * Every string here is rendered as TEXT by the transcript. Nothing is HTML.
11
- *
12
- * The coding plane's vocabulary (apps/coding): `status` for a lifecycle move
13
- * (started, routed, done, error, stopped, paused), `tool-call` for a step of the
14
- * run itself, `event` for one line of the agent's own structured narration (the
15
- * harness's JSON stream: its messages, reasoning, commands, edits, tool calls and
16
- * plan), and `log` for raw output, which only the Terminal's log shows. A
17
- * person's steering is a `control` {command, message}. A run on a project with a
18
- * site ends `done` with `live` when it published what it pushed, or `unpublished`
19
- * saying why it did not.
20
- *
21
- * `cards` is the transcript as it is drawn: the run's steps and the agent's
22
- * items, never its log.
23
- */
1
+ import type { Say } from '@hanzo/i18n/react';
24
2
  import type { Event } from './sessions.ts';
25
3
  /** The payload as an object. A live frame may carry it as a JSON string. */
26
4
  export declare function decode(payload: unknown): Record<string, unknown> | string | null;
@@ -32,17 +10,17 @@ export declare function decode(payload: unknown): Record<string, unknown> | stri
32
10
  */
33
11
  export declare function vendor(error: string): boolean;
34
12
  /** An error as the run page may show it: its own words, or the plain line when they are a vendor's. */
35
- export declare const scrub: (error: string) => string;
13
+ export declare const scrub: (error: string, t?: Say) => string;
36
14
  /**
37
15
  * A run's error as one plain line with what to do, never the log it came from:
38
16
  * that goes in the Error step beside it (cards), unless it is a vendor's.
39
17
  */
40
- export declare function plain(error: string): string;
18
+ export declare function plain(error: string, t?: Say): string;
41
19
  /**
42
20
  * One event's sentence, or '' when it says nothing a person needs. `mode` is
43
21
  * the run's, from its record: a plan run's final status says the plan.
44
22
  */
45
- export declare function said(e: Pick<Event, 'kind' | 'payload'>, mode?: string): string;
23
+ export declare function said(e: Pick<Event, 'kind' | 'payload'>, mode?: string, t?: Say): string;
46
24
  /** One line of the shell: a command the run started, or output that followed it. */
47
25
  export interface ShellLine {
48
26
  role: 'cmd' | 'out';
@@ -89,7 +67,7 @@ export interface Outcome {
89
67
  */
90
68
  export declare function outcome(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): Outcome;
91
69
  /** Why the run failed, as its last status says it, or '' when that status is not a failure. */
92
- export declare function failure(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): string;
70
+ export declare function failure(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], t?: Say): string;
93
71
  /** The turns, ordered and deduped: a recorded read and the live feed overlap. */
94
72
  export declare function merge<T extends Pick<Event, 'id' | 'seq'>>(...lists: T[][]): T[];
95
73
  /** The actor, short enough to label a block: `hanzo/2d4d67ab`, not the whole subject. */
@@ -176,7 +154,7 @@ export type Card = {
176
154
  * is drawn only if the turn then fails. The final status carries the agent's
177
155
  * answer, drawn once. `log` is never drawn here.
178
156
  */
179
- export declare function cards(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], mode?: string): Card[];
157
+ export declare function cards(events: Pick<Event, 'kind' | 'payload' | 'seq'>[], mode?: string, t?: Say): Card[];
180
158
  /** The plan a plan run answered with, from its final status, or ''. */
181
159
  export declare function answer(events: Pick<Event, 'kind' | 'payload' | 'seq'>[]): string;
182
160
  export interface Settled {
package/lib/api/turn.js CHANGED
@@ -1,3 +1,34 @@
1
+ /**
2
+ * What a run's events SAY, as text a person reads.
3
+ *
4
+ * A curated projection, never a dump: an event carries the machinery a run
5
+ * happened on — hosts, paths, whole file bodies — and printing it would put
6
+ * someone's filesystem in front of anyone who can read the run. Each kind gets a
7
+ * sentence built from the few fields that describe the outcome; the rest is
8
+ * dropped by omission, because a projection cannot leak a field it never reads.
9
+ *
10
+ * Every string here is rendered as TEXT by the transcript. Nothing is HTML.
11
+ *
12
+ * The coding plane's vocabulary (apps/coding): `status` for a lifecycle move
13
+ * (started, routed, done, error, stopped, paused), `tool-call` for a step of the
14
+ * run itself, `event` for one line of the agent's own structured narration (the
15
+ * harness's JSON stream: its messages, reasoning, commands, edits, tool calls and
16
+ * plan), and `log` for raw output, which only the Terminal's log shows. A
17
+ * person's steering is a `control` {command, message}. A run on a project with a
18
+ * site ends `done` with `live` when it published what it pushed, or `unpublished`
19
+ * saying why it did not.
20
+ *
21
+ * `cards` is the transcript as it is drawn: the run's steps and the agent's
22
+ * items, never its log.
23
+ *
24
+ * The sentences are this client's, said in the language of the `t` a caller
25
+ * hands (@hanzo/i18n; English without one); what the run and the agent said
26
+ * is drawn as given. A step's name is a name the transcript keys on (`Error`
27
+ * opens itself), so it stays English, marked (`phrase`), and the transcript
28
+ * draws it through `t`.
29
+ */
30
+ import { phrase } from '@hanzo/i18n';
31
+ import { english } from '../say.js';
1
32
  const str = (v) => (typeof v === 'string' ? v : '');
2
33
  const obj = (v) => (v && typeof v === 'object' ? v : {});
3
34
  /** The payload as an object. A live frame may carry it as a JSON string. */
@@ -22,40 +53,65 @@ const base = (path) => {
22
53
  const cut = path.replace(/\/+$/, '');
23
54
  return cut.slice(cut.lastIndexOf('/') + 1);
24
55
  };
25
- function status(b, mode) {
56
+ /**
57
+ * A finished run's line, by whether it pushed a branch (Done when it did not),
58
+ * its pull request (none, one that could not be opened, its address) and its
59
+ * site (none, not published and why, published): each a whole line, so a
60
+ * language puts the parts in its own order.
61
+ */
62
+ const FINISHED = {
63
+ pushed: {
64
+ none: { none: phrase('Pushed {branch}'), out: phrase('Pushed {branch} — not published: {why}'), live: phrase('Pushed {branch} — published') },
65
+ broke: {
66
+ none: phrase('Pushed {branch} — the pull request could not be opened'),
67
+ out: phrase('Pushed {branch} — the pull request could not be opened — not published: {why}'),
68
+ live: phrase('Pushed {branch} — the pull request could not be opened — published'),
69
+ },
70
+ pr: { none: phrase('Pushed {branch} — {pr}'), out: phrase('Pushed {branch} — {pr} — not published: {why}'), live: phrase('Pushed {branch} — {pr} — published') },
71
+ },
72
+ done: {
73
+ none: { none: phrase('Done'), out: phrase('Done — not published: {why}'), live: phrase('Done — published') },
74
+ broke: {
75
+ none: phrase('Done — the pull request could not be opened'),
76
+ out: phrase('Done — the pull request could not be opened — not published: {why}'),
77
+ live: phrase('Done — the pull request could not be opened — published'),
78
+ },
79
+ pr: { none: phrase('Done — {pr}'), out: phrase('Done — {pr} — not published: {why}'), live: phrase('Done — {pr} — published') },
80
+ },
81
+ };
82
+ function status(b, mode, t) {
26
83
  const branch = str(b.branch);
27
84
  switch (str(b.status)) {
28
85
  case 'started':
29
- return branch ? `Started on ${branch}` : 'Started';
86
+ return branch ? t('Started on {branch}', { branch }) : t('Started');
30
87
  case 'routed':
31
- return 'Sent to a machine';
88
+ return t('Sent to a machine');
32
89
  case 'done': {
33
90
  // A plan's answer IS its final status: the run read and wrote nothing.
34
91
  // Whether the run planned is the RECORD's word — any member of the org
35
92
  // can append an event saying `mode: plan`, and its text is not an answer.
36
93
  // A setup is a plan put to one question, and answers the same way.
37
94
  if (mode === 'plan')
38
- return str(b.plan) || 'Planned — the run answered with no plan';
95
+ return str(b.plan) || t('Planned — the run answered with no plan');
39
96
  if (mode === 'setup')
40
- return str(b.plan) || 'Set up — the run answered with no environment';
97
+ return str(b.plan) || t('Set up — the run answered with no environment');
41
98
  if (b.changed === false)
42
- return 'Done — nothing to change';
99
+ return t('Done — nothing to change');
43
100
  const pr = str(b.pr);
44
- const tail = str(b.prError) ? ' — the pull request could not be opened' : pr ? ` — ${pr}` : '';
45
101
  // A run on a project with a site publishes what it pushed, or says why not.
46
102
  const why = str(b.unpublished);
47
- const site = why ? ` — not published: ${why}` : str(b.live) ? ' — published' : '';
48
- return branch ? `Pushed ${branch}${tail}${site}` : `Done${tail}${site}`;
103
+ const line = FINISHED[branch ? 'pushed' : 'done'][str(b.prError) ? 'broke' : pr ? 'pr' : 'none'][why ? 'out' : str(b.live) ? 'live' : 'none'];
104
+ return t(line, { branch, pr, why });
49
105
  }
50
106
  case 'error':
51
- return plain(str(b.error));
107
+ return plain(str(b.error), t);
52
108
  case 'stopped':
53
- return branch && b.changed ? `Stopped — work kept on ${branch}` : 'Stopped';
109
+ return branch && b.changed ? t('Stopped — work kept on {branch}', { branch }) : t('Stopped');
54
110
  case 'paused':
55
- return branch && b.changed ? `Paused — work kept on ${branch}` : 'Paused';
111
+ return branch && b.changed ? t('Paused — work kept on {branch}', { branch }) : t('Paused');
56
112
  case 'followed':
57
113
  // A run redirected from a chat thread is carried on by a follow-up (apps/coding steer.go).
58
- return str(b.next) ? 'Continued in a follow-up run' : 'The follow-up run could not start';
114
+ return str(b.next) ? t('Continued in a follow-up run') : t('The follow-up run could not start');
59
115
  }
60
116
  return str(b.status);
61
117
  }
@@ -69,35 +125,35 @@ export function vendor(error) {
69
125
  return /open\s?router|anthropic|openai|azure|bedrock|vertex|gemini|mistral|cohere|deepinfra|fireworks|together\.ai|groq|x-ratelimit|upstream|\bprovider\b|\bsk-[A-Za-z0-9_-]{6,}|"(error|code|message)"\s*:/i.test(error);
70
126
  }
71
127
  /** An error as the run page may show it: its own words, or the plain line when they are a vendor's. */
72
- export const scrub = (error) => (vendor(error) ? plain(error) : error);
128
+ export const scrub = (error, t = english) => (vendor(error) ? plain(error, t) : error);
73
129
  /**
74
130
  * A run's error as one plain line with what to do, never the log it came from:
75
131
  * that goes in the Error step beside it (cards), unless it is a vendor's.
76
132
  */
77
- export function plain(error) {
133
+ export function plain(error, t = english) {
78
134
  if (!error)
79
- return 'The run hit an error';
135
+ return t('The run hit an error');
80
136
  const ours = /providers_exhausted|every provider refused|temporarily unavailable/i.test(error);
81
137
  if (/free sandboxes .*are used/i.test(error))
82
- return 'This plan’s free sandboxes are used for now. Add credit to keep building, or try again later.';
138
+ return t('This plan’s free sandboxes are used for now. Add credit to keep building, or try again later.');
83
139
  if (!ours && /\b402 Payment Required|insufficient (credit|balance)|top up/i.test(error))
84
- return 'Your balance is out of credit. Add credit, then try again.';
140
+ return t('Your balance is out of credit. Add credit, then try again.');
85
141
  if (ours || /\b(429 Too Many Requests|503 Service Unavailable)|stream disconnected|retries exhausted|rate limit/i.test(error))
86
- return 'The model did not answer. Try again in a moment, or pick another model.';
87
- return 'The run stopped with an error. Try again, or follow up with what to change.';
142
+ return t('The model did not answer. Try again in a moment, or pick another model.');
143
+ return t('The run stopped with an error. Try again, or follow up with what to change.');
88
144
  }
89
145
  /**
90
146
  * One event's sentence, or '' when it says nothing a person needs. `mode` is
91
147
  * the run's, from its record: a plan run's final status says the plan.
92
148
  */
93
- export function said(e, mode = '') {
149
+ export function said(e, mode = '', t = english) {
94
150
  const body = decode(e.payload);
95
151
  if (typeof body === 'string')
96
152
  return body;
97
153
  if (!body)
98
154
  return '';
99
155
  if (e.kind === 'status')
100
- return status(body, mode);
156
+ return status(body, mode, t);
101
157
  for (const key of ['message', 'text', 'content', 'result', 'command']) {
102
158
  const v = body[key];
103
159
  if (typeof v === 'string' && v)
@@ -106,13 +162,13 @@ export function said(e, mode = '') {
106
162
  if (str(body.type) === 'done' && Array.isArray(body.changed)) {
107
163
  const names = body.changed.map((f) => base(str(f))).filter(Boolean);
108
164
  return names.length === 0
109
- ? 'Done — no files changed'
165
+ ? t('Done — no files changed')
110
166
  : names.length <= 3
111
- ? `Done — changed ${names.join(', ')}`
112
- : `Done — changed ${names.length} files`;
167
+ ? t('Done — changed {files}', { files: names.join(', ') })
168
+ : t('Done — changed {n} files', { n: names.length });
113
169
  }
114
170
  if (str(body.type) === 'error')
115
- return scrub(str(body.error)) || 'The run hit an error';
171
+ return scrub(str(body.error), t) || t('The run hit an error');
116
172
  return str(body.step) || str(body.name);
117
173
  }
118
174
  /**
@@ -200,7 +256,7 @@ export function outcome(events) {
200
256
  return out;
201
257
  }
202
258
  /** Why the run failed, as its last status says it, or '' when that status is not a failure. */
203
- export function failure(events) {
259
+ export function failure(events, t = english) {
204
260
  let out = '';
205
261
  for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
206
262
  if (e.kind !== 'status')
@@ -208,7 +264,7 @@ export function failure(events) {
208
264
  const b = decode(e.payload);
209
265
  if (!b || typeof b === 'string' || !str(b.status))
210
266
  continue;
211
- out = str(b.status) === 'error' ? scrub(str(b.error)) || 'The run hit an error' : '';
267
+ out = str(b.status) === 'error' ? scrub(str(b.error), t) || t('The run hit an error') : '';
212
268
  }
213
269
  return out;
214
270
  }
@@ -242,19 +298,19 @@ export function pull(url, repo) {
242
298
  return { href: '', label: '' };
243
299
  return { href: url, label: `#${m[3]}` };
244
300
  }
245
- /** What the run's own steps are called on a card. */
301
+ /** What the run's own steps are called on a card: English, marked (`phrase`); the transcript draws them through `t`. */
246
302
  const NAMES = {
247
- lease: 'Sandbox',
248
- workspace: 'Workspace',
249
- clone: 'Clone',
250
- branch: 'Branch',
251
- secrets: 'Secrets',
252
- install: 'Install',
253
- start: 'Start',
254
- skills: 'Skills',
255
- artifacts: 'Save artifacts',
256
- push: 'Push',
257
- kept: 'Keep the sandbox',
303
+ lease: phrase('Sandbox'),
304
+ workspace: phrase('Workspace'),
305
+ clone: phrase('Clone'),
306
+ branch: phrase('Branch'),
307
+ secrets: phrase('Secrets'),
308
+ install: phrase('Install'),
309
+ start: phrase('Start'),
310
+ skills: phrase('Skills'),
311
+ artifacts: phrase('Save artifacts'),
312
+ push: phrase('Push'),
313
+ kept: phrase('Keep the sandbox'),
258
314
  };
259
315
  /** Steps another card already says: `leased` finishes the lease, `done` is the final status, `ended` and `exit` are housekeeping. */
260
316
  const QUIET = new Set(['leased', 'done', 'ended', 'exit']);
@@ -290,9 +346,9 @@ function item(it, key) {
290
346
  return { kind: 'edit', key, files: changes.map((c) => rel(str(obj(c).path))).filter(Boolean), ran: itemRan(it) };
291
347
  }
292
348
  case 'mcp_tool_call':
293
- return { kind: 'step', key, name: str(it.tool) || 'Tool', detail: str(it.server), output: '', ran: itemRan(it) };
349
+ return { kind: 'step', key, name: str(it.tool) || phrase('Tool'), detail: str(it.server), output: '', ran: itemRan(it) };
294
350
  case 'web_search':
295
- return { kind: 'step', key, name: 'Search', detail: str(it.query), output: '', ran: 'done' };
351
+ return { kind: 'step', key, name: phrase('Search'), detail: str(it.query), output: '', ran: 'done' };
296
352
  case 'todo_list': {
297
353
  const items = Array.isArray(it.items) ? it.items : [];
298
354
  return { kind: 'todo', key, items: items.map((x) => ({ text: str(obj(x).text), done: obj(x).completed === true })).filter((x) => x.text) };
@@ -313,7 +369,7 @@ function item(it, key) {
313
369
  * is drawn only if the turn then fails. The final status carries the agent's
314
370
  * answer, drawn once. `log` is never drawn here.
315
371
  */
316
- export function cards(events, mode = '') {
372
+ export function cards(events, mode = '', t = english) {
317
373
  const out = [];
318
374
  let seq = 0;
319
375
  let n = 0;
@@ -334,10 +390,10 @@ export function cards(events, mode = '') {
334
390
  if (failures.has(message))
335
391
  return;
336
392
  failures.add(message);
337
- out.push({ kind: 'note', key: mint(), text: plain(message) });
393
+ out.push({ kind: 'note', key: mint(), text: plain(message, t) });
338
394
  // The reason, read whole, when it is the run's own; a vendor's words stay off the page.
339
395
  if (message && !vendor(message))
340
- out.push({ kind: 'step', key: mint(), name: 'Error', detail: '', output: message, ran: 'error' });
396
+ out.push({ kind: 'step', key: mint(), name: phrase('Error'), detail: '', output: message, ran: 'error' });
341
397
  };
342
398
  for (const e of [...events].sort((a, b) => a.seq - b.seq)) {
343
399
  seq = e.seq;
@@ -421,7 +477,7 @@ export function cards(events, mode = '') {
421
477
  failed(str(b.error));
422
478
  break;
423
479
  }
424
- const line = status(b, mode);
480
+ const line = status(b, mode, t);
425
481
  if (line)
426
482
  out.push({ kind: 'note', key: mint(), text: line });
427
483
  break;
@@ -434,11 +490,11 @@ export function cards(events, mode = '') {
434
490
  if (command === 'message' && said)
435
491
  steered.add(person(said));
436
492
  else if (command === 'pause')
437
- out.push({ kind: 'note', key: mint(), text: 'Pause asked' });
493
+ out.push({ kind: 'note', key: mint(), text: t('Pause asked') });
438
494
  else if (command === 'resume')
439
- out.push({ kind: 'note', key: mint(), text: 'Resume asked' });
495
+ out.push({ kind: 'note', key: mint(), text: t('Resume asked') });
440
496
  else if (command === 'stop')
441
- out.push({ kind: 'note', key: mint(), text: 'Stop asked' });
497
+ out.push({ kind: 'note', key: mint(), text: t('Stop asked') });
442
498
  break;
443
499
  }
444
500
  case 'message': {
@@ -1,12 +1,3 @@
1
- /**
2
- * A person's verdict on what a run said, recorded on the platform's own event
3
- * bus in the org it was said in.
4
- *
5
- * POST /v1/event {type: 'track', event: 'build.verdict', sessionId, properties}
6
- *
7
- * The receipt says whether it landed; a verdict that did not is reported, not
8
- * shown as recorded.
9
- */
10
1
  import { type Target } from './call.ts';
11
2
  export type Verdict = 'up' | 'down' | null;
12
3
  export declare function verdict(t: Target, session: string, project: string, v: Verdict): Promise<void>;
@@ -7,6 +7,7 @@
7
7
  * The receipt says whether it landed; a verdict that did not is reported, not
8
8
  * shown as recorded.
9
9
  */
10
+ import { phrase } from '@hanzo/i18n';
10
11
  import { call } from './call.js';
11
12
  export async function verdict(t, session, project, v) {
12
13
  const out = await call(t, 'POST', '/v1/event', {
@@ -16,5 +17,5 @@ export async function verdict(t, session, project, v) {
16
17
  properties: { verdict: v ?? 'cleared', project },
17
18
  });
18
19
  if (!out || !out.accepted)
19
- throw new Error('The verdict was not recorded');
20
+ throw new Error(phrase('The verdict was not recorded'));
20
21
  }
@@ -1,16 +1,3 @@
1
- /**
2
- * The organization's webhooks: https endpoints that each matching platform event
3
- * is POSTed to, signed.
4
- *
5
- * GET /v1/webhook {data: [endpoint]}, newest first, secrets withheld
6
- * POST /v1/webhook {url, events, description} → 201, the secret shown this once
7
- * DELETE /v1/webhook/{id} 204
8
- * POST /v1/webhook/{id}/test one signed test event, sent now → {delivered, httpStatus, durationMs, error}
9
- * GET /v1/webhook/{id}/deliveries {data: [attempt]}, newest first
10
- *
11
- * The org is the caller's; an endpoint another org owns reads as not found.
12
- * Events are subject patterns (`commerce.order.>`); none means every event.
13
- */
14
1
  import { type Target } from './call.ts';
15
2
  export interface Endpoint {
16
3
  id: string;
@@ -36,7 +23,7 @@ export interface Draft {
36
23
  events: string[];
37
24
  description: string;
38
25
  }
39
- /** Why a draft cannot be sent, or ''. The platform refuses the same things. */
26
+ /** Why a draft cannot be sent, or ''. The platform refuses the same things. English, marked (`phrase`): a screen says it through `t`. */
40
27
  export declare function refuse(d: Draft): string;
41
28
  export declare function add(t: Target, d: Draft): Promise<Endpoint>;
42
29
  export declare function remove(t: Target, id: string): Promise<void>;
@@ -11,6 +11,7 @@
11
11
  * The org is the caller's; an endpoint another org owns reads as not found.
12
12
  * Events are subject patterns (`commerce.order.>`); none means every event.
13
13
  */
14
+ import { phrase } from '@hanzo/i18n';
14
15
  import { call, query, seg } from './call.js';
15
16
  const str = (v) => (typeof v === 'string' ? v : '');
16
17
  const num = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : 0);
@@ -38,19 +39,19 @@ export async function endpoints(t) {
38
39
  export function patterns(typed) {
39
40
  return [...new Set(typed.split(/[\s,]+/).map((s) => s.trim()).filter(Boolean))];
40
41
  }
41
- /** Why a draft cannot be sent, or ''. The platform refuses the same things. */
42
+ /** Why a draft cannot be sent, or ''. The platform refuses the same things. English, marked (`phrase`): a screen says it through `t`. */
42
43
  export function refuse(d) {
43
44
  let u;
44
45
  try {
45
46
  u = new URL(d.url.trim());
46
47
  }
47
48
  catch {
48
- return 'Enter the https address to deliver to';
49
+ return phrase('Enter the https address to deliver to');
49
50
  }
50
51
  if (u.protocol !== 'https:')
51
- return 'A webhook is delivered over https only';
52
+ return phrase('A webhook is delivered over https only');
52
53
  if (d.events.length > 64)
53
- return 'At most 64 event patterns';
54
+ return phrase('At most 64 event patterns');
54
55
  return '';
55
56
  }
56
57
  export async function add(t, d) {
package/lib/ask.js CHANGED
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
5
5
  * the reason is read where the act was asked for.
6
6
  */
7
7
  import { SizableText, XStack } from '@hanzo/gui';
8
+ import { useI18n } from '@hanzo/i18n/react';
8
9
  import { Button, Dialog, DialogContent, DialogTitle, Input } from '@hanzo/ui';
9
10
  import { useEffect, useState } from 'react';
10
11
  /** One act in flight, and what refused it. */
@@ -32,6 +33,7 @@ function Said({ note }) {
32
33
  return (_jsx(SizableText, { size: "$1", color: "$soft", role: "status", children: note }));
33
34
  }
34
35
  export function Confirm({ open, onOpenChange, title, says, act, run, }) {
36
+ const { t } = useI18n();
35
37
  const { working, note, setNote, go } = useAct(() => onOpenChange(false));
36
38
  const close = (o) => {
37
39
  if (working)
@@ -39,9 +41,10 @@ export function Confirm({ open, onOpenChange, title, says, act, run, }) {
39
41
  setNote('');
40
42
  onOpenChange(o);
41
43
  };
42
- return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => void go(run), children: act })] })] }) }));
44
+ return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(SizableText, { size: "$2", color: "$soft", children: says }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: t('Cancel') }), _jsx(Button, { size: "sm", variant: "destructive", disabled: working, onPress: () => void go(run), children: act })] })] }) }));
43
45
  }
44
46
  export function Rename({ open, onOpenChange, title, name, run, }) {
47
+ const { t } = useI18n();
45
48
  const [value, setValue] = useState(name);
46
49
  const { working, note, setNote, go } = useAct(() => onOpenChange(false));
47
50
  useEffect(() => {
@@ -57,13 +60,13 @@ export function Rename({ open, onOpenChange, title, name, run, }) {
57
60
  const save = () => {
58
61
  const next = value.trim();
59
62
  if (!next)
60
- return setNote('It needs a name');
63
+ return setNote(t('It needs a name'));
61
64
  if (next === name)
62
65
  return close(false);
63
66
  void go(() => run(next));
64
67
  };
65
- return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(Input, { autoFocus: true, value: value, onChangeText: setValue, "aria-label": "Name", onKeyDown: (e) => {
68
+ return (_jsx(Dialog, { open: open, onOpenChange: close, children: _jsxs(DialogContent, { maxW: 420, showCloseButton: false, children: [_jsx(DialogTitle, { children: title }), _jsx(Input, { autoFocus: true, value: value, onChangeText: setValue, "aria-label": t('Name'), onKeyDown: (e) => {
66
69
  if (e.key === 'Enter')
67
70
  save();
68
- } }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: "Cancel" }), _jsx(Button, { size: "sm", disabled: working, onPress: save, children: "Save" })] })] }) }));
71
+ } }), _jsx(Said, { note: note }), _jsxs(XStack, { gap: "$2", justify: "flex-end", children: [_jsx(Button, { size: "sm", variant: "ghost", disabled: working, onPress: () => close(false), children: t('Cancel') }), _jsx(Button, { size: "sm", disabled: working, onPress: save, children: t('Save') })] })] }) }));
69
72
  }