@arcadeai/hypotenuse 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/hypotenuse.js +214 -32
  2. package/package.json +1 -1
@@ -343,7 +343,7 @@ import { Command } from "commander";
343
343
  // package.json
344
344
  var package_default = {
345
345
  name: "@arcadeai/hypotenuse",
346
- version: "0.12.0",
346
+ version: "0.13.0",
347
347
  description: "Hypotenuse \u2014 run coding agents on your machine for Linear agent sessions",
348
348
  type: "module",
349
349
  license: "Apache-2.0",
@@ -852,14 +852,28 @@ var CANCEL_REASONS = [
852
852
  "dashboard",
853
853
  "delegate_removed",
854
854
  "issue_closed",
855
+ "unassigned",
855
856
  "integration_revoked",
856
857
  "superseded"
857
858
  ];
858
859
  var Cancel = z3.looseObject({
859
860
  workItemId: z3.number().int().positive(),
860
861
  reason: z3.union([z3.enum(CANCEL_REASONS), z3.string().min(1).max(64)]),
861
- by: z3.string().min(1).max(200).optional()
862
+ by: z3.string().min(1).max(200).optional(),
863
+ said: z3.string().min(1).max(2000).optional()
862
864
  });
865
+ var STOP_QUOTE_MAX = 500;
866
+ function stopQuote(said) {
867
+ const text = said?.trim();
868
+ if (!text || /^stop[.!]*$/i.test(text))
869
+ return "";
870
+ const cut = text.length > STOP_QUOTE_MAX ? `${text.slice(0, STOP_QUOTE_MAX)}\u2026` : text;
871
+ return `
872
+
873
+ > You said: ${cut.split(`
874
+ `).join(`
875
+ > `)}`;
876
+ }
863
877
  var Answer = z3.looseObject({
864
878
  workItemId: z3.number().int().positive(),
865
879
  activityId: z3.number().int().positive(),
@@ -912,10 +926,14 @@ var SelectMetadata = z5.strictObject({
912
926
  value: z5.string().trim().min(1).max(1024)
913
927
  })).min(1).max(25)
914
928
  });
929
+ var AuthProvider = z5.string().regex(/^[a-z0-9][a-z0-9._-]{0,63}$/).describe("An agent id, `github`, or a git host");
915
930
  var AuthMetadata = z5.strictObject({
916
- url: z5.url(),
931
+ url: z5.url().optional(),
932
+ provider: AuthProvider.optional(),
917
933
  userId: z5.string().max(255).optional(),
918
934
  providerName: z5.string().max(255).optional()
935
+ }).refine((m) => m.url !== undefined || m.provider !== undefined, {
936
+ message: "an auth elicitation needs a url or a provider"
919
937
  });
920
938
  var ThoughtEvent = z5.strictObject({
921
939
  ...envelope,
@@ -3524,7 +3542,7 @@ function stopActivity(ctx) {
3524
3542
  case "stop":
3525
3543
  return {
3526
3544
  type: "response",
3527
- body: `Stopped at your request. Changes so far are ${where}.`
3545
+ body: `Stopped at your request. Changes so far are ${where}.${stopQuote(ctx.note)}`
3528
3546
  };
3529
3547
  case "kill":
3530
3548
  return {
@@ -4451,7 +4469,7 @@ function formFields(request) {
4451
4469
  };
4452
4470
  });
4453
4471
  }
4454
- function elicitationQuestion(request, agentTitle) {
4472
+ function elicitationQuestion(request, agentTitle, provider = "agent") {
4455
4473
  const message = truncate(request.message.trim() || "The agent has a question.", 60000);
4456
4474
  const cancelled = () => ({ action: "cancel" });
4457
4475
  const declined = () => ({ action: "decline" });
@@ -4468,7 +4486,11 @@ function elicitationQuestion(request, agentTitle) {
4468
4486
  type: "elicitation",
4469
4487
  body: message,
4470
4488
  signal: "auth",
4471
- signalMetadata: { url, providerName: truncate(agentTitle, 255) }
4489
+ signalMetadata: {
4490
+ url,
4491
+ provider,
4492
+ providerName: truncate(agentTitle, 255)
4493
+ }
4472
4494
  } : { type: "elicitation", body: `${message}
4473
4495
 
4474
4496
  ${url}` },
@@ -4705,6 +4727,14 @@ var SILENT_KINDS = new Set([
4705
4727
  "compaction_update",
4706
4728
  "compaction_summary_chunk"
4707
4729
  ]);
4730
+ function responseSignature(who) {
4731
+ const version = who.agentVersion ? ` ${who.agentVersion}` : "";
4732
+ return `
4733
+
4734
+ +++ \u2014 Hypotenuse (${who.role}, ${who.agentTitle})
4735
+ An AI coding agent: \`${who.agentId}\`${version} on runner \`${who.runnerName}\`, delegated through Hypotenuse.
4736
+ +++`;
4737
+ }
4708
4738
 
4709
4739
  class TurnMapper {
4710
4740
  options;
@@ -4868,9 +4898,10 @@ class TurnMapper {
4868
4898
  switch (stopReason) {
4869
4899
  case "end_turn": {
4870
4900
  const last = this.lastActionSummary;
4901
+ const signature = context.signature ?? "";
4871
4902
  events.push({
4872
4903
  type: "response",
4873
- body: truncate(said || (last ? `Done. Last step: ${last}.` : "Done."), 60000)
4904
+ body: `${truncate(said || (last ? `Done. Last step: ${last}.` : "Done."), 60000 - signature.length)}${signature}`
4874
4905
  });
4875
4906
  break;
4876
4907
  }
@@ -5079,26 +5110,101 @@ class PrDetector {
5079
5110
  }
5080
5111
  }
5081
5112
 
5113
+ // src/acp/preflight.ts
5114
+ function providerSlug(agentId) {
5115
+ const slug = agentId.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[^a-z0-9]+/, "").slice(0, 64);
5116
+ return slug || "agent";
5117
+ }
5118
+ var PREFLIGHT_TIMEOUT_MS = 20000;
5119
+ var AUTH_FAILURE = /authentication failed|could not read username|could not read password|permission denied \(publickey|invalid username or password|terminal prompts disabled|support for password authentication was removed|http basic: access denied|403 forbidden|returned error: 40[13]/i;
5120
+ function isGitAuthFailure(output) {
5121
+ return AUTH_FAILURE.test(output);
5122
+ }
5123
+ function remoteHost(remote) {
5124
+ try {
5125
+ const url = new URL(remote);
5126
+ if (url.protocol === "file:")
5127
+ return null;
5128
+ return url.hostname.toLowerCase() || null;
5129
+ } catch {
5130
+ const scp = remote.match(/^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)/);
5131
+ return scp ? scp[1].toLowerCase() : null;
5132
+ }
5133
+ }
5134
+ function remoteProvider(remote) {
5135
+ const host = remoteHost(remote) ?? "git";
5136
+ if (host === "github.com")
5137
+ return { provider: "github", providerName: "GitHub" };
5138
+ return { provider: host, providerName: host };
5139
+ }
5140
+ var runCommand2 = async (argv, options) => {
5141
+ try {
5142
+ const proc = Bun.spawn(argv, {
5143
+ cwd: options.cwd,
5144
+ env: {
5145
+ ...options.env,
5146
+ GIT_TERMINAL_PROMPT: "0",
5147
+ GH_PROMPT_DISABLED: "1"
5148
+ },
5149
+ stdin: "ignore",
5150
+ stdout: "pipe",
5151
+ stderr: "pipe"
5152
+ });
5153
+ const timer = setTimeout(() => proc.kill(), options.timeoutMs);
5154
+ const [stdout, stderr, code] = await Promise.all([
5155
+ new Response(proc.stdout).text(),
5156
+ new Response(proc.stderr).text(),
5157
+ proc.exited
5158
+ ]).finally(() => clearTimeout(timer));
5159
+ return { code, stdout, stderr };
5160
+ } catch (error) {
5161
+ return {
5162
+ code: null,
5163
+ stdout: "",
5164
+ stderr: error instanceof Error ? error.message : String(error)
5165
+ };
5166
+ }
5167
+ };
5168
+ async function checkRepositoryAuth(input, run = runCommand2) {
5169
+ const { remote, cwd, env, runnerName } = input;
5170
+ if (!remote || remoteHost(remote) === null)
5171
+ return null;
5172
+ const provider = remoteProvider(remote);
5173
+ const listed = await run(["git", "ls-remote", "--heads", remote], {
5174
+ cwd,
5175
+ env,
5176
+ timeoutMs: PREFLIGHT_TIMEOUT_MS
5177
+ });
5178
+ if (listed.code !== 0 && isGitAuthFailure(`${listed.stderr}
5179
+ ${listed.stdout}`)) {
5180
+ return {
5181
+ ...provider,
5182
+ body: `Runner ${runnerName} can't sign in to ${provider.providerName} to reach \`${remote}\`, so it could fetch and push nothing. Its owner can fix it on that machine \u2014 the link says how. Reply here once it's done and I'll carry on.`
5183
+ };
5184
+ }
5185
+ if (provider.provider !== "github" || !input.pullRequests)
5186
+ return null;
5187
+ const gh = await run(["gh", "auth", "status", "--hostname", "github.com"], {
5188
+ cwd,
5189
+ env,
5190
+ timeoutMs: PREFLIGHT_TIMEOUT_MS
5191
+ });
5192
+ if (gh.code === null || gh.code === 127)
5193
+ return null;
5194
+ if (gh.code !== 0) {
5195
+ return {
5196
+ ...provider,
5197
+ body: `The GitHub CLI on runner ${runnerName} is not signed in, so I couldn't open a pull request. Its owner can fix it on that machine \u2014 the link says how. Reply here once it's done and I'll carry on.`
5198
+ };
5199
+ }
5200
+ return null;
5201
+ }
5202
+
5082
5203
  // src/acp/templates/manager.md
5083
5204
  var manager_default = "You are the planning agent for this Linear workspace, acting as the app user `@hypotenuse`, on\n{{issue.identifier}} \u2014 {{issue.title}}. You are an AI agent; say so if anyone asks.\n\n## What you produce\n\n- **A PRD.** When the issue concerns a whole project, write it into the project's content with\n `linear_update_project` (read it first with `linear_get_project`). When a person already owns that content, put\n the PRD in a document attached to the project with `linear_create_document`, and revise it later with\n `linear_update_document`. Sections: Problem, Goals, Non-goals, User stories, Requirements, Open questions,\n Rollout. Read the code in your working directory so the PRD fits the codebase as it is.\n- **A breakdown into issues**, each small enough for one agent session: sub-issues of this issue\n (`linear_create_sub_issue`) or new issues (`linear_create_issue`), edited with `linear_update_issue`. Every issue\n has a title and a description with an **Acceptance criteria** checklist (`- [ ] \u2026`); labels chosen from\n `linear_list_labels`, never invented; an estimate where the team uses them; and `blocks` or `related` relations\n (`linear_create_relation`) where order matters. Look for existing issues first with `linear_search_issues` and\n `linear_get_issue`, so you do not file duplicates.\n\n## How you ask\n\nAsk clarifying questions with `linear_ask_user`, with `options` whenever the answer is a choice. Ask everything you\nneed at once, not one question per turn, then end your turn. Read what people already said with\n`linear_list_comments`.\n\n## What you never do\n\n- Write or change code, or run commands: your working directory is read-only. If answering a question needs\n something run, file a small spike issue and dispatch it.\n- Open or merge pull requests: `linear_report_pr` is for workers, and a worker's pull requests reach you in its\n review.\n- Move an issue to a completed or canceled state with `linear_set_issue_state` unless the tool tells you that is\n permitted. Moving an issue to an \"In Review\"-type started state is fine.\n- Dispatch an issue you did not create, or that a person has not delegated to you or put under this issue.\n- Have more dispatched issues in progress at once than the tool allows; it will tell you when you are at the\n limit.\n\n## How you dispatch\n\nDispatch only issues whose acceptance criteria are written: one `hypotenuse_dispatch` per issue, with\n`instructions` for context the worker needs that does not belong in the issue. A worker session starts on the\nissue on whichever runner can take it. Keep this session's plan current with `linear_set_plan` if your own plan\nupdates do not already appear.\n\n## How you review\n\nWhen a dispatched worker finishes, you get a review turn with its result: its outcome, final response, plan, and\npull requests. Check the result against the issue's acceptance criteria (`linear_get_issue`), then do one of:\n\n- **Accept**: say so in a `linear_comment` on the worker's issue (\"Reviewed against the acceptance criteria: \u2026\"),\n optionally moving it to an \"In Review\"-type state with `linear_set_issue_state`. A person merges the pull\n request.\n- **Request changes**: one `hypotenuse_dispatch` with `mode: \"changes\"` and specific `instructions` \u2014 what is\n missing or wrong, against which criterion. The same worker session continues on the same branch.\n- **File follow-ups**: new issues with `linear_create_issue`, related to the original with\n `linear_create_relation` (`related`). Dispatch them now only if they are ready.\n\nWhen requesting changes is refused because the review round limit is reached, stop sending the work back: end\nwith `linear_ask_user`, addressed to the person who started this session, explaining what is still wrong.\n\nEnd every review turn with a response that says which you chose, and why.\n\n## Writing in Linear\n\nTo mention a person or an issue, use its Linear URL, not an @name. Finish every turn with a short summary of what\nyou decided and what happens next.\n";
5084
5205
 
5085
5206
  // src/acp/templates/worker.md
5086
- var worker_default = `Implement {{issue.identifier}} \u2014 {{issue.title}} \u2014 in the working directory.
5087
-
5088
- - Read the issue and the Linear context below before you change anything, and keep a plan current as you go.
5089
- \`linear_get_issue\` and \`linear_list_comments\` read the issue and its thread when you need more.
5090
- - Commit in small steps; reference {{issue.identifier}} in every commit message.
5091
- - When the work is ready, push the branch and open a pull request that links {{issue.url}}, then call
5092
- \`linear_report_pr\` with its URL straight away.
5093
- - If a decision belongs to a person (scope, product behaviour, anything destructive), ask once, clearly, with
5094
- \`linear_ask_user\` \u2014 with options when there are a few clear choices \u2014 and end your turn there. Their answer
5095
- arrives as your next prompt.
5096
- - Your thoughts, tool calls, and final message already appear in Linear. Do not repeat them with \`linear_comment\`;
5097
- that is for a question someone left elsewhere on the issue, or a blocker for someone not following this session.
5098
- - To mention a person or an issue in Linear, use its Linear URL, not an @name.
5099
- - Finish with a short summary: what changed, what you tested, and what a reviewer should look at first. Do not list
5100
- every file.
5101
- `;
5207
+ var worker_default = "Implement {{issue.identifier}} \u2014 {{issue.title}} \u2014 in the working directory. You are an AI agent working as the\nLinear app user `@hypotenuse`; say so if anyone asks.\n\n- Read the issue and the Linear context below before you change anything, and keep a plan current as you go.\n `linear_get_issue` and `linear_list_comments` read the issue and its thread when you need more.\n- Commit in small steps; reference {{issue.identifier}} in every commit message.\n- When the work is ready, push the branch and open a pull request that links {{issue.url}}, then call\n `linear_report_pr` with its URL straight away.\n- If a decision belongs to a person (scope, product behaviour, anything destructive), ask once, clearly, with\n `linear_ask_user` \u2014 with options when there are a few clear choices \u2014 and end your turn there. Their answer\n arrives as your next prompt.\n- Your thoughts, tool calls, and final message already appear in Linear. Do not repeat them with `linear_comment`;\n that is for a question someone left elsewhere on the issue, or a blocker for someone not following this session.\n- To mention a person or an issue in Linear, use its Linear URL, not an @name.\n- Finish with a short summary: what changed, what you tested, and what a reviewer should look at first. Do not list\n every file.\n";
5102
5208
 
5103
5209
  // src/acp/prompt.ts
5104
5210
  var DEFAULT_TEMPLATES = {
@@ -5298,6 +5404,21 @@ class TurnFailure extends Error {
5298
5404
  }
5299
5405
  }
5300
5406
 
5407
+ class AuthRequired extends Error {
5408
+ need;
5409
+ unavailable;
5410
+ constructor(need, unavailable) {
5411
+ super(need.body);
5412
+ this.need = need;
5413
+ this.unavailable = unavailable;
5414
+ }
5415
+ }
5416
+ var SIGNED_IN_BODY = "Signed in \u2014 continuing.";
5417
+ function resumesAfterAuth(assignment) {
5418
+ const last = (assignment.history ?? []).filter((h) => h.direction === "outbound").at(-1);
5419
+ return last?.type === "elicitation" && last.signal === "auth";
5420
+ }
5421
+
5301
5422
  class AcpSessionExecutor {
5302
5423
  options;
5303
5424
  constructor(options = {}) {
@@ -5337,6 +5458,7 @@ class Turn {
5337
5458
  conn = null;
5338
5459
  acpSessionId = null;
5339
5460
  agentTitle;
5461
+ agentVersion = null;
5340
5462
  endReason = null;
5341
5463
  ended;
5342
5464
  endNow;
@@ -5446,9 +5568,30 @@ class Turn {
5446
5568
  });
5447
5569
  await this.prepare();
5448
5570
  await this.start();
5571
+ if (resumesAfterAuth(ctx.assignment))
5572
+ this.emit({ type: "thought", body: SIGNED_IN_BODY, ephemeral: false });
5449
5573
  timer = setInterval(() => this.tick(), this.options.tickMs ?? TICK_MS);
5450
5574
  return await this.prompt();
5451
5575
  } catch (error) {
5576
+ if (error instanceof AuthRequired && !this.endReason) {
5577
+ if (error.unavailable)
5578
+ ctx.agentAvailability(false, error.unavailable);
5579
+ this.log.note(`needs a login: ${error.need.provider}`);
5580
+ this.emitAll(this.mapper.flushThought(false));
5581
+ this.emit({
5582
+ type: "elicitation",
5583
+ body: error.need.body,
5584
+ signal: "auth",
5585
+ signalMetadata: {
5586
+ provider: error.need.provider,
5587
+ providerName: error.need.providerName.slice(0, 255)
5588
+ }
5589
+ });
5590
+ this.mapper.askedUser = true;
5591
+ return this.result("completed");
5592
+ }
5593
+ if (error instanceof AuthRequired)
5594
+ return await this.stopped();
5452
5595
  if (error instanceof TurnFailure) {
5453
5596
  if (error.unavailable)
5454
5597
  ctx.agentAvailability(false, error.unavailable);
@@ -5482,6 +5625,14 @@ class Turn {
5482
5625
  env: (branch) => sessionEnvironment(ctx.env, sessionVariables(assignment, branch))
5483
5626
  });
5484
5627
  } catch (error) {
5628
+ const remote = ctx.assignment.repository?.remote ?? null;
5629
+ if (error instanceof WorkspaceError && remote && isGitAuthFailure(error.body)) {
5630
+ const provider = remoteProvider(remote);
5631
+ throw new AuthRequired({
5632
+ ...provider,
5633
+ body: `Runner ${ctx.runnerName} can't sign in to ${provider.providerName} to fetch \`${remote}\`. Its owner can fix it on that machine \u2014 the link says how. Reply here once it's done and I'll carry on.`
5634
+ });
5635
+ }
5485
5636
  if (error instanceof WorkspaceError)
5486
5637
  throw new TurnFailure(error.body);
5487
5638
  throw error;
@@ -5491,6 +5642,17 @@ class Turn {
5491
5642
  } else {
5492
5643
  mkdirSync6(this.cwd, { recursive: true, mode: 448 });
5493
5644
  }
5645
+ if (this.endReason)
5646
+ throw new TurnFailure("stopped");
5647
+ const need = await checkRepositoryAuth({
5648
+ remote: this.workspace?.repository?.remote ?? null,
5649
+ cwd: this.cwd,
5650
+ env: ctx.env,
5651
+ runnerName: ctx.runnerName,
5652
+ pullRequests: (assignment.role ?? "worker") === "worker"
5653
+ }, this.options.run);
5654
+ if (need)
5655
+ throw new AuthRequired(need);
5494
5656
  if (this.endReason)
5495
5657
  throw new TurnFailure("stopped");
5496
5658
  this.mapperOptions.cwd = this.cwd;
@@ -5566,7 +5728,7 @@ class Turn {
5566
5728
  throw this.fsError(error);
5567
5729
  }
5568
5730
  },
5569
- createElicitation: (params) => this.questions.ask(elicitationQuestion(params, this.agentTitle)),
5731
+ createElicitation: (params) => this.questions.ask(elicitationQuestion(params, this.agentTitle, providerSlug(this.agentId))),
5570
5732
  unknown: (method) => this.log.note(`unhandled ${method}`)
5571
5733
  }
5572
5734
  });
@@ -5579,6 +5741,7 @@ class Turn {
5579
5741
  ctx.setState("starting");
5580
5742
  const init = await this.guard(conn.initialize(package_default.version), launch.startupTimeoutMs, `${this.agentId} did not answer initialize within ${launch.startupTimeoutMs / 1000}s (\`${launch.command.join(" ")}\`).`, true);
5581
5743
  this.agentTitle = init.agentInfo?.title ?? init.agentInfo?.name ?? this.agentId;
5744
+ this.agentVersion = init.agentInfo?.version ?? null;
5582
5745
  ctx.log.debug(`${this.agentId} initialized: ${init.agentInfo?.name ?? "?"} ${init.agentInfo?.version ?? ""}`, { session: assignment.agentSessionId });
5583
5746
  await this.openSession(init, launch.authMethod);
5584
5747
  ctx.agentAvailability(true);
@@ -5620,21 +5783,30 @@ class Turn {
5620
5783
  await this.chooseSession(init.agentCapabilities ?? {});
5621
5784
  return;
5622
5785
  } catch (error) {
5623
- if (!isAuthRequired(error) || authenticated) {
5786
+ if (isAuthRequired(error) && authenticated) {
5787
+ throw new AuthRequired(this.loginNeed(), "not logged in");
5788
+ }
5789
+ if (!isAuthRequired(error)) {
5624
5790
  if (error instanceof TurnFailure)
5625
5791
  throw error;
5626
5792
  throw new TurnFailure(`${this.agentId} could not open a session: ${errorMessage(error)}${quoteTail(this.log.tail())}`);
5627
5793
  }
5628
5794
  const method = this.authMethodFor(init.authMethods ?? [], configuredMethod);
5629
- if (!method) {
5630
- throw new TurnFailure(`${this.agentId} on runner ${this.ctx.runnerName} is not logged in. On that machine, run: \`hypotenuse agents login ${this.agentId}\``, "not logged in");
5631
- }
5795
+ if (!method)
5796
+ throw new AuthRequired(this.loginNeed(), "not logged in");
5632
5797
  this.log.note(`authenticate ${method}`);
5633
5798
  await this.guard(this.conn.authenticate(method), 0, "");
5634
5799
  authenticated = true;
5635
5800
  }
5636
5801
  }
5637
5802
  }
5803
+ loginNeed() {
5804
+ return {
5805
+ provider: providerSlug(this.agentId),
5806
+ providerName: this.agentTitle,
5807
+ body: `${this.agentTitle} on runner ${this.ctx.runnerName} is not logged in. Its owner can sign in on that machine with \`hypotenuse agents login ${this.agentId}\` \u2014 the link has the steps. Reply here once it's done and I'll carry on.`
5808
+ };
5809
+ }
5638
5810
  authMethodFor(methods, configured) {
5639
5811
  const agentRun = methods.filter((m) => m.type !== "terminal");
5640
5812
  if (configured)
@@ -5772,7 +5944,16 @@ class Turn {
5772
5944
  const stop = answer.stopReason === "cancelled" ? "end_turn" : answer.stopReason;
5773
5945
  if (this.mapper.askedUser && this.mapper.hasMessage)
5774
5946
  this.log.note(`said after asking (kept out of Linear): ${this.mapper.message().slice(0, 2000)}`);
5775
- this.emitAll(this.mapper.finish(stop, { branch: this.branch }));
5947
+ this.emitAll(this.mapper.finish(stop, {
5948
+ branch: this.branch,
5949
+ signature: responseSignature({
5950
+ role: this.ctx.assignment.role ?? "worker",
5951
+ agentId: this.agentId,
5952
+ agentTitle: this.agentTitle,
5953
+ agentVersion: this.agentVersion,
5954
+ runnerName: this.ctx.runnerName
5955
+ })
5956
+ }));
5776
5957
  return stop === "end_turn" ? this.result("completed") : this.result("failed", `stopReason: ${stop}`);
5777
5958
  }
5778
5959
  repositoryName() {
@@ -8094,8 +8275,9 @@ class Supervisor {
8094
8275
  this.silence(id);
8095
8276
  for (const cancel of answer.cancels) {
8096
8277
  const slot = this.slots.get(cancel.workItemId);
8097
- if (slot && !slot.reason && cancel.by)
8098
- slot.killNote = cancel.by;
8278
+ const note = cancel.by ?? cancel.said;
8279
+ if (slot && !slot.reason && note)
8280
+ slot.killNote = note;
8099
8281
  this.stop(cancel.workItemId, cancel.reason);
8100
8282
  }
8101
8283
  for (const reply of answer.answers ?? [])
@@ -10248,7 +10430,7 @@ function describeEvent(event, payload) {
10248
10430
  }
10249
10431
  function registerEventCommands(program) {
10250
10432
  const event = program.command("event").description("The selected project's event log: Linear's webhooks and how each was routed");
10251
- withPaging(event.command("list").description("List webhook events, newest first").option("--type <eventType>", "only this event type, e.g. AgentSessionEvent, Issue, PermissionChange").option("--outcome <outcome>", "only this outcome: handled, ignored, duplicate_of_dispatch, failed, pending").option("--hide-ignored", "leave out events Hypotenuse did not act on").option("--session <id>", "only events about this agent session").option("--since <time>", "only events since: ISO-8601, epoch ms, or 7d / 12h / 30m")).action(async function() {
10433
+ withPaging(event.command("list").description("List webhook events, newest first").option("--type <eventType>", "only this event type, e.g. AgentSessionEvent, Issue, PermissionChange").option("--outcome <outcome>", "only this outcome: handled, ignored, duplicate_of_dispatch, noted, mention_without_session, assigned_not_delegated, disengaged, failed, pending").option("--hide-ignored", "leave out events Hypotenuse did not act on").option("--session <id>", "only events about this agent session").option("--since <time>", "only events since: ISO-8601, epoch ms, or 7d / 12h / 30m")).action(async function() {
10252
10434
  await runCommand(this, async (ctx) => {
10253
10435
  const opts = this.opts();
10254
10436
  const projectId = await ctx.projectId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcadeai/hypotenuse",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Hypotenuse — run coding agents on your machine for Linear agent sessions",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",