orkestrator 2.28.0-beta → 2.30.0-beta

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.
package/dist/client.js CHANGED
@@ -6557,6 +6557,70 @@ var PUBLIC_ACTIONS = {
6557
6557
  milestone: "D",
6558
6558
  summary: "Read one page of a session's turns, each with the backend's change projection."
6559
6559
  },
6560
+ "attention.list": {
6561
+ version: 1,
6562
+ mutation: false,
6563
+ effects: [],
6564
+ observation: "immediate",
6565
+ milestone: "D",
6566
+ summary: "What needs attention: pending questions and approvals, the caller's runs that settled and environments whose setup finished, since a cursor."
6567
+ },
6568
+ "ticket.list": {
6569
+ version: 1,
6570
+ mutation: false,
6571
+ effects: [],
6572
+ observation: "immediate",
6573
+ milestone: "D",
6574
+ summary: "List a project's Kanban tickets."
6575
+ },
6576
+ "ticket.get": {
6577
+ version: 1,
6578
+ mutation: false,
6579
+ effects: [],
6580
+ observation: "immediate",
6581
+ milestone: "D",
6582
+ summary: "Read one Kanban ticket with its description, criteria and latest comments."
6583
+ },
6584
+ "ticket.create": {
6585
+ version: 1,
6586
+ mutation: true,
6587
+ effects: ["metadata"],
6588
+ observation: "receipt",
6589
+ milestone: "D",
6590
+ summary: "Create a Kanban ticket in a project."
6591
+ },
6592
+ "ticket.update": {
6593
+ version: 1,
6594
+ mutation: true,
6595
+ effects: ["metadata"],
6596
+ observation: "receipt",
6597
+ milestone: "D",
6598
+ summary: "Change a Kanban ticket's title, description, criteria or column."
6599
+ },
6600
+ "ticket.comment": {
6601
+ version: 1,
6602
+ mutation: true,
6603
+ effects: ["metadata"],
6604
+ observation: "receipt",
6605
+ milestone: "D",
6606
+ summary: "Append a comment to a Kanban ticket."
6607
+ },
6608
+ "mail.mailboxes": {
6609
+ version: 1,
6610
+ mutation: false,
6611
+ effects: [],
6612
+ observation: "immediate",
6613
+ milestone: "D",
6614
+ summary: "List agent mailboxes a message can be sent to."
6615
+ },
6616
+ "mail.send": {
6617
+ version: 1,
6618
+ mutation: true,
6619
+ effects: ["metadata"],
6620
+ observation: "receipt",
6621
+ milestone: "D",
6622
+ summary: "Place a message in one tab's inbox; external messages are never injected into a turn."
6623
+ },
6560
6624
  "run.get": {
6561
6625
  version: 1,
6562
6626
  mutation: false,
@@ -6752,6 +6816,8 @@ var PUBLIC_API_LIMITS = Object.freeze({
6752
6816
  transcriptTextMaxChars: 20000,
6753
6817
  interactionsMax: 32,
6754
6818
  historyMaxEntries: 100,
6819
+ attentionDefaultItems: 20,
6820
+ attentionMaxItems: 50,
6755
6821
  responseMaxBytes: 8 * 1024 * 1024,
6756
6822
  requestMaxBytes: 1024 * 1024,
6757
6823
  execArgvMaxItems: 256,
@@ -7444,7 +7510,7 @@ import path4 from "path";
7444
7510
  // ../../apps/backend/package.json
7445
7511
  var package_default = {
7446
7512
  name: "@orkestrator/backend",
7447
- version: "2.28.0-beta",
7513
+ version: "2.30.0-beta",
7448
7514
  private: true,
7449
7515
  type: "module",
7450
7516
  exports: {
@@ -7459,7 +7525,7 @@ var package_default = {
7459
7525
  "start:web-public": "bun dist/main.js --tailscale-serve --allowed-origins https://orkestrator.dev,https://www.orkestrator.dev",
7460
7526
  test: "bun test --preload ../../tests/setup-node.ts src tests --parallel",
7461
7527
  "test:workspace": "bun test --preload ../../tests/setup-node.ts src tests --only-failures ${ORKESTRATOR_TEST_TIMINGS_DIR:+--timings=$ORKESTRATOR_TEST_TIMINGS_DIR/backend.json --update-timings} $ORKESTRATOR_BACKEND_TEST_IGNORE --parallel=${ORKESTRATOR_BACKEND_TEST_WORKERS:-${ORKESTRATOR_TEST_WORKERS:-2}}",
7462
- typecheck: "bunx tsc --noEmit -p tsconfig.json && bunx tsc --noEmit -p tsconfig.test.json"
7528
+ typecheck: "bun check -p tsconfig.json && bun check -p tsconfig.test.json"
7463
7529
  },
7464
7530
  dependencies: {
7465
7531
  "@modelcontextprotocol/node": "2.1.1",
@@ -7998,269 +8064,37 @@ function runFixProcess(argv, version) {
7998
8064
  });
7999
8065
  }
8000
8066
 
8001
- // ../protocol/src/public-api-resources.ts
8002
- var PUBLIC_CHANGE_SCOPES = Object.freeze(["environment"]);
8003
- var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
8004
- function agentSettingDescriptors(scope) {
8005
- const application = "next-session";
8006
- return [
8007
- {
8008
- key: "agent.defaultAgent",
8009
- scope,
8010
- type: "agent",
8011
- inherits: true,
8012
- application,
8013
- description: "Agent platform used when a launch does not name one."
8014
- },
8015
- ...AGENT_PLATFORM_KEYS.flatMap((platform) => [
8016
- {
8017
- key: `agent.${platform}.mode`,
8018
- scope,
8019
- type: "agent-mode",
8020
- inherits: true,
8021
- application,
8022
- description: `Launch mode (native or terminal) for ${platform}.`
8023
- },
8024
- {
8025
- key: `agent.${platform}.model`,
8026
- scope,
8027
- type: "string",
8028
- inherits: true,
8029
- application,
8030
- description: `Default model for new ${platform} sessions.`
8031
- },
8032
- {
8033
- key: `agent.${platform}.reasoningEffort`,
8034
- scope,
8035
- type: "string",
8036
- inherits: true,
8037
- application,
8038
- description: `Default reasoning level for new ${platform} sessions.`
8039
- },
8040
- {
8041
- key: `agent.${platform}.fastMode`,
8042
- scope,
8043
- type: "boolean",
8044
- inherits: true,
8045
- application,
8046
- description: `Default speed for new ${platform} sessions (true is Fast).`
8047
- }
8048
- ])
8049
- ];
8050
- }
8051
- var PUBLIC_PROJECT_SETTINGS = Object.freeze([
8052
- {
8053
- key: "defaultBranch",
8054
- scope: "project",
8055
- type: "string",
8056
- inherits: false,
8057
- application: "next-environment",
8058
- description: "Branch new environments are created from."
8059
- },
8060
- {
8061
- key: "prBaseBranch",
8062
- scope: "project",
8063
- type: "string",
8064
- inherits: false,
8065
- application: "applied",
8066
- description: "Base branch for pull requests and diff baselines."
8067
- },
8068
- {
8069
- key: "defaultPortMappings",
8070
- scope: "project",
8071
- type: "port-mappings",
8072
- inherits: false,
8073
- environmentTypes: ["container"],
8074
- application: "next-environment",
8075
- description: "Port mappings copied into new container environments."
8076
- },
8077
- {
8078
- key: "filesToCopy",
8079
- scope: "project",
8080
- type: "string-list",
8081
- inherits: false,
8082
- application: "next-environment",
8083
- description: "Untracked files copied into new environments."
8084
- },
8085
- {
8086
- key: "entryPort",
8087
- scope: "project",
8088
- type: "port",
8089
- inherits: false,
8090
- application: "next-environment",
8091
- description: "Default preview entry port for new environments."
8092
- },
8093
- ...agentSettingDescriptors("project")
8094
- ]);
8095
- var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
8096
- {
8097
- key: "portMappings",
8098
- scope: "environment",
8099
- type: "port-mappings",
8100
- inherits: false,
8101
- environmentTypes: ["container"],
8102
- application: "next-start",
8103
- description: "Container port mappings; a running container adopts them on recreate."
8104
- },
8105
- {
8106
- key: "allowedDomains",
8107
- scope: "environment",
8108
- type: "domain-list",
8109
- inherits: true,
8110
- environmentTypes: ["container"],
8111
- application: "next-start",
8112
- description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
8113
- },
8114
- ...agentSettingDescriptors("environment")
8115
- ]);
8116
-
8117
- // src/client/format.ts
8118
- function table(headers, rows) {
8119
- if (rows.length === 0)
8120
- return ["(none)"];
8121
- const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
8122
- const line = (cells) => cells.map((cell, index) => {
8123
- const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
8124
- return text.padEnd(widths[index]);
8125
- }).join(" ").trimEnd();
8126
- return [line(headers), ...rows.map(line)];
8067
+ // src/client/wait.ts
8068
+ class ObservationStopped extends Error {
8069
+ reason;
8070
+ signalName;
8071
+ constructor(reason, signalName) {
8072
+ super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
8073
+ this.reason = reason;
8074
+ this.signalName = signalName;
8075
+ }
8127
8076
  }
8128
- function keyValues(entries) {
8129
- const width = Math.max(...entries.map(([key]) => key.length));
8130
- return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
8131
- const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
8132
- return `${key.padEnd(width)} ${text}`;
8077
+ function defaultSleep(ms, signal) {
8078
+ return new Promise((resolve) => {
8079
+ if (signal?.aborted) {
8080
+ resolve();
8081
+ return;
8082
+ }
8083
+ const timer = setTimeout(done, ms);
8084
+ function done() {
8085
+ clearTimeout(timer);
8086
+ signal?.removeEventListener("abort", done);
8087
+ resolve();
8088
+ }
8089
+ signal?.addEventListener("abort", done, { once: true });
8133
8090
  });
8134
8091
  }
8135
- function projectLines(project) {
8136
- return keyValues([
8137
- ["id", project.id],
8138
- ["name", project.name],
8139
- ["remote", project.gitUrl],
8140
- ["path", project.localPath],
8141
- ["folder", project.folder],
8142
- ["environments", project.environmentCount],
8143
- ["revision", project.revision]
8144
- ]);
8145
- }
8146
- function projectRows(projects) {
8147
- return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
8148
- project.id,
8149
- project.name,
8150
- String(project.environmentCount),
8151
- project.localPath ?? "-"
8152
- ]));
8153
- }
8154
- function environmentLines(environment) {
8155
- return keyValues([
8156
- ["id", environment.id],
8157
- ["project", environment.projectId],
8158
- ["name", environment.name],
8159
- ["branch", environment.branch],
8160
- ["type", environment.environmentType],
8161
- ["status", environment.status],
8162
- ["ready", environment.ready],
8163
- ["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
8164
- ["activity", environment.activity.state],
8165
- ["lifecycle error", environment.lifecycle.error],
8166
- ["workspace", environment.workspacePath],
8167
- ["base", environment.base.commit ?? environment.base.branch],
8168
- ["startup session", environment.startupSession?.sessionId]
8169
- ]);
8092
+ function signalName(signal) {
8093
+ return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
8170
8094
  }
8171
- function environmentRows(environments) {
8172
- return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
8173
- environment.id,
8174
- environment.name,
8175
- environment.environmentType,
8176
- environment.status,
8177
- environment.ready ? "yes" : "no"
8178
- ]));
8179
- }
8180
- function sessionRows(sessions) {
8181
- return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
8182
- session.id,
8183
- session.agent,
8184
- session.tabId,
8185
- session.activity,
8186
- session.title ?? ""
8187
- ]));
8188
- }
8189
- function sessionLines(session) {
8190
- return keyValues([
8191
- ["id", session.id],
8192
- ["environment", session.environmentId],
8193
- ["tab", session.tabId],
8194
- ["agent", session.agent],
8195
- ["title", session.title],
8196
- ["activity", session.activity],
8197
- ["latest request", session.latestRequestId],
8198
- ["pending interactions", session.pendingInteractionCount],
8199
- ["recoverable dispatch", session.recoverableDispatch]
8200
- ]);
8201
- }
8202
- function receiptLines(receipt) {
8203
- return keyValues([
8204
- ["operation", receipt.operationId],
8205
- ["action", receipt.action],
8206
- ["state", receipt.state],
8207
- ["stage", receipt.stage],
8208
- ["request", `${receipt.requestId} (${receipt.namespace})`],
8209
- ["replayed", receipt.replayed],
8210
- ["resources", receipt.resources],
8211
- ["dispatch", receipt.dispatch?.state],
8212
- ["execution", receipt.execution?.state],
8213
- ["evidence", receipt.execution?.evidence],
8214
- ["exit code", receipt.execution?.exitCode],
8215
- ["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
8216
- ["updated", receipt.updatedAt],
8217
- ["retained until", receipt.retainedUntil]
8218
- ]);
8219
- }
8220
- function settingsLines(snapshot) {
8221
- return [
8222
- `revision ${snapshot.revision}`,
8223
- ...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
8224
- setting.key,
8225
- setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
8226
- setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
8227
- setting.source,
8228
- setting.application
8229
- ]))
8230
- ];
8231
- }
8232
-
8233
- // src/client/wait.ts
8234
- class ObservationStopped extends Error {
8235
- reason;
8236
- signalName;
8237
- constructor(reason, signalName) {
8238
- super(reason === "deadline" ? "Observation deadline passed" : "Observation interrupted");
8239
- this.reason = reason;
8240
- this.signalName = signalName;
8241
- }
8242
- }
8243
- function defaultSleep(ms, signal) {
8244
- return new Promise((resolve) => {
8245
- if (signal?.aborted) {
8246
- resolve();
8247
- return;
8248
- }
8249
- const timer = setTimeout(done, ms);
8250
- function done() {
8251
- clearTimeout(timer);
8252
- signal?.removeEventListener("abort", done);
8253
- resolve();
8254
- }
8255
- signal?.addEventListener("abort", done, { once: true });
8256
- });
8257
- }
8258
- function signalName(signal) {
8259
- return signal?.reason === "SIGTERM" ? "SIGTERM" : "SIGINT";
8260
- }
8261
- function throwIfInterrupted(signal) {
8262
- if (signal?.aborted)
8263
- throw new CliError("observation-interrupted", "Interrupted");
8095
+ function throwIfInterrupted(signal) {
8096
+ if (signal?.aborted)
8097
+ throw new CliError("observation-interrupted", "Interrupted");
8264
8098
  }
8265
8099
  async function observe(check, options2) {
8266
8100
  if (options2.timeoutMs > PUBLIC_API_LIMITS.waitMaxMs) {
@@ -8284,6 +8118,61 @@ async function observe(check, options2) {
8284
8118
  }
8285
8119
  }
8286
8120
 
8121
+ // ../protocol/src/public-api-wait.ts
8122
+ function isTerminalPublicReceipt(receipt) {
8123
+ return !["admitted", "running", "unknown"].includes(receipt.state);
8124
+ }
8125
+ function classifyReceiptForWait(receipt, options2 = {}) {
8126
+ if (isTerminalPublicReceipt(receipt)) {
8127
+ return { kind: "terminal", succeeded: receipt.state === "succeeded" };
8128
+ }
8129
+ const execution = receipt.execution;
8130
+ if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
8131
+ return {
8132
+ kind: "stopped",
8133
+ code: "interaction-required",
8134
+ message: "The run is waiting for an answer to a pending interaction",
8135
+ interactions: execution.interactions ?? []
8136
+ };
8137
+ }
8138
+ if (receipt.state === "unknown" && receipt.dispatch?.state === "unknown") {
8139
+ return {
8140
+ kind: "stopped",
8141
+ code: "dispatch-unknown",
8142
+ message: "The provider may or may not have received the prompt; retry it under the same key or discard it",
8143
+ retryable: true
8144
+ };
8145
+ }
8146
+ if (receipt.state === "unknown") {
8147
+ return {
8148
+ kind: "stopped",
8149
+ code: "run-unknown",
8150
+ message: receipt.error?.message ?? "The outcome of this run is not known"
8151
+ };
8152
+ }
8153
+ if (execution?.state === "unsupported") {
8154
+ return {
8155
+ kind: "stopped",
8156
+ code: "capability-unavailable",
8157
+ message: execution.reason ?? "Completion of this run cannot be observed for its provider"
8158
+ };
8159
+ }
8160
+ return { kind: "pending" };
8161
+ }
8162
+ function terminalReceiptErrorCode(receipt) {
8163
+ if (receipt.state === "succeeded")
8164
+ return null;
8165
+ if (receipt.state === "partial")
8166
+ return "partial-failure";
8167
+ if (receipt.state === "cancelled")
8168
+ return "run-cancelled";
8169
+ if (receipt.state === "interrupted")
8170
+ return "run-interrupted";
8171
+ if (receipt.state === "unknown")
8172
+ return "run-unknown";
8173
+ return receipt.error?.code ?? "operation-failed";
8174
+ }
8175
+
8287
8176
  // src/client/commands/common.ts
8288
8177
  var REQUEST_ID_OPTION = {
8289
8178
  name: "request-id",
@@ -8394,7 +8283,7 @@ async function readOperation(session, operationId, timeoutMs) {
8394
8283
  return receipt;
8395
8284
  }
8396
8285
  function isTerminalReceipt(receipt) {
8397
- return !["admitted", "running", "unknown"].includes(receipt.state);
8286
+ return isTerminalPublicReceipt(receipt);
8398
8287
  }
8399
8288
  function stoppedError(error, action, receipt, what) {
8400
8289
  if (error.reason === "signal") {
@@ -8410,12 +8299,15 @@ function stoppedError(error, action, receipt, what) {
8410
8299
  });
8411
8300
  }
8412
8301
  function receiptFailure(receipt) {
8413
- if (receipt.state === "succeeded")
8302
+ const code = terminalReceiptErrorCode(receipt);
8303
+ if (!code)
8414
8304
  return;
8415
8305
  const message = receipt.error?.message ?? `Operation ended ${receipt.state}`;
8416
- const code = receipt.state === "partial" ? "partial-failure" : receipt.state === "cancelled" ? "run-cancelled" : receipt.state === "interrupted" ? "run-interrupted" : receipt.state === "unknown" ? "run-unknown" : receipt.error?.code ?? "operation-failed";
8417
8306
  return new CliError(code, message, { receipt, action: receipt.action });
8418
8307
  }
8308
+ var CLI_STOP_MESSAGES = {
8309
+ "dispatch-unknown": "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`"
8310
+ };
8419
8311
  async function waitForOperation(context, session, initial, timeoutMs, options2 = {}) {
8420
8312
  let latest = initial;
8421
8313
  const deadline = Date.now() + timeoutMs;
@@ -8432,94 +8324,423 @@ async function waitForOperation(context, session, initial, timeoutMs, options2 =
8432
8324
  throw new ObservationStopped("deadline");
8433
8325
  throw error;
8434
8326
  }
8435
- if (isTerminalReceipt(latest))
8327
+ const verdict = classifyReceiptForWait(latest, options2);
8328
+ if (verdict.kind === "terminal")
8436
8329
  return { done: true, value: latest };
8437
- const execution = latest.execution;
8438
- if (execution?.state === "waiting-for-input" && !options2.continueOnInteraction) {
8439
- throw new CliError("interaction-required", "The run is waiting for an answer to a pending interaction", {
8330
+ if (verdict.kind === "stopped") {
8331
+ throw new CliError(verdict.code, CLI_STOP_MESSAGES[verdict.code] ?? verdict.message, {
8440
8332
  receipt: latest,
8441
8333
  action: latest.action,
8442
- details: { interactions: execution.interactions ?? [] }
8334
+ ...verdict.retryable ? { retryable: true } : {},
8335
+ ...verdict.interactions ? { details: { interactions: verdict.interactions } } : {}
8443
8336
  });
8444
8337
  }
8445
- if (latest.state === "unknown" && latest.dispatch?.state === "unknown") {
8446
- throw new CliError("dispatch-unknown", "The provider may or may not have received the prompt; use `run retry` (same key) or `run discard`", { receipt: latest, action: latest.action, retryable: true });
8338
+ return { done: false };
8339
+ }, { timeoutMs, signal: context.signal });
8340
+ } catch (error) {
8341
+ if (error instanceof ObservationStopped) {
8342
+ throw stoppedError(error, latest.action, latest, "the operation");
8343
+ }
8344
+ throw error;
8345
+ }
8346
+ }
8347
+ async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
8348
+ let latestReceipt = receipt;
8349
+ const conditionMet = (environment) => {
8350
+ if (condition === "deleted")
8351
+ return environment === null;
8352
+ if (!environment)
8353
+ return false;
8354
+ if (condition === "running")
8355
+ return environment.status === "running";
8356
+ if (condition === "stopped")
8357
+ return environment.status === "stopped";
8358
+ return environment.ready;
8359
+ };
8360
+ try {
8361
+ return await observe(async () => {
8362
+ if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
8363
+ latestReceipt = await readOperation(session, latestReceipt.operationId);
8364
+ }
8365
+ if (latestReceipt && isTerminalReceipt(latestReceipt)) {
8366
+ const failure = receiptFailure(latestReceipt);
8367
+ if (failure)
8368
+ throw failure;
8447
8369
  }
8448
- if (latest.state === "unknown") {
8449
- throw new CliError("run-unknown", latest.error?.message ?? "The outcome of this run is not known", {
8450
- receipt: latest,
8451
- action: latest.action
8370
+ let environment;
8371
+ try {
8372
+ environment = await session.read("environment.get", {
8373
+ environmentId
8452
8374
  });
8375
+ } catch (error) {
8376
+ if (error instanceof CliError && error.code === "not-found")
8377
+ environment = null;
8378
+ else
8379
+ throw error;
8453
8380
  }
8454
- if (execution?.state === "unsupported") {
8455
- throw new CliError("capability-unavailable", execution.reason ?? "Completion of this run cannot be observed for its provider", { receipt: latest, action: latest.action });
8381
+ if (condition !== "deleted" && !environment) {
8382
+ throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
8383
+ }
8384
+ if (environment && (condition === "ready" || condition === "running")) {
8385
+ if (environment.setup.phase === "failed" || environment.status === "error") {
8386
+ throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
8387
+ }
8388
+ }
8389
+ const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
8390
+ if (operationSettled && conditionMet(environment)) {
8391
+ return {
8392
+ done: true,
8393
+ value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
8394
+ };
8456
8395
  }
8457
8396
  return { done: false };
8458
8397
  }, { timeoutMs, signal: context.signal });
8459
8398
  } catch (error) {
8460
8399
  if (error instanceof ObservationStopped) {
8461
- throw stoppedError(error, latest.action, latest, "the operation");
8400
+ throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
8462
8401
  }
8463
8402
  throw error;
8464
8403
  }
8465
8404
  }
8466
- async function waitForEnvironment(context, session, environmentId, condition, receipt, timeoutMs) {
8467
- let latestReceipt = receipt;
8468
- const conditionMet = (environment) => {
8469
- if (condition === "deleted")
8470
- return environment === null;
8471
- if (!environment)
8472
- return false;
8473
- if (condition === "running")
8474
- return environment.status === "running";
8475
- if (condition === "stopped")
8476
- return environment.status === "stopped";
8477
- return environment.ready;
8478
- };
8479
- try {
8480
- return await observe(async () => {
8481
- if (latestReceipt && !isTerminalReceipt(latestReceipt)) {
8482
- latestReceipt = await readOperation(session, latestReceipt.operationId);
8483
- }
8484
- if (latestReceipt && isTerminalReceipt(latestReceipt)) {
8485
- const failure = receiptFailure(latestReceipt);
8486
- if (failure)
8487
- throw failure;
8488
- }
8489
- let environment;
8490
- try {
8491
- environment = await session.read("environment.get", {
8492
- environmentId
8493
- });
8494
- } catch (error) {
8495
- if (error instanceof CliError && error.code === "not-found")
8496
- environment = null;
8497
- else
8498
- throw error;
8499
- }
8500
- if (condition !== "deleted" && !environment) {
8501
- throw new CliError("not-found", "The environment no longer exists", latestReceipt ? { receipt: latestReceipt } : {});
8502
- }
8503
- if (environment && (condition === "ready" || condition === "running")) {
8504
- if (environment.setup.phase === "failed" || environment.status === "error") {
8505
- throw new CliError("setup-failed", environment.lifecycle.error ?? "Environment setup failed", { ...latestReceipt ? { receipt: latestReceipt } : {}, details: { environmentId } });
8506
- }
8507
- }
8508
- const operationSettled = !latestReceipt || isTerminalReceipt(latestReceipt) || condition === "running" && latestReceipt.stage === "setup";
8509
- if (operationSettled && conditionMet(environment)) {
8510
- return {
8511
- done: true,
8512
- value: { environment, ...latestReceipt ? { receipt: latestReceipt } : {} }
8513
- };
8514
- }
8515
- return { done: false };
8516
- }, { timeoutMs, signal: context.signal });
8517
- } catch (error) {
8518
- if (error instanceof ObservationStopped) {
8519
- throw stoppedError(error, latestReceipt?.action ?? "environment.get", latestReceipt, `the environment (${condition})`);
8520
- }
8521
- throw error;
8522
- }
8405
+
8406
+ // src/client/commands/attention.ts
8407
+ function attentionLine(item) {
8408
+ if (item.kind === "interaction") {
8409
+ return `${item.blocking ? "waiting" : "pending"} ${item.agent} in ${item.environmentName}: ${item.title} (${item.sessionId} ${item.interactionId} rev ${item.revision})`;
8410
+ }
8411
+ if (item.kind === "setup") {
8412
+ return `setup ${item.phase} ${item.environmentName} (${item.environmentId})`;
8413
+ }
8414
+ return `run ${item.state} ${item.action}${item.errorCode ? ` ${item.errorCode}` : ""} (${item.operationId})`;
8415
+ }
8416
+ function itemKey(item) {
8417
+ if (item.kind === "interaction")
8418
+ return `interaction:${item.interactionId}:${item.revision}`;
8419
+ if (item.kind === "setup")
8420
+ return `setup:${item.environmentId}:${item.at}`;
8421
+ return `run:${item.operationId}:${item.state}`;
8422
+ }
8423
+ var attentionCommands = [
8424
+ {
8425
+ path: ["attention"],
8426
+ action: "attention.list",
8427
+ summary: "What needs attention: questions, approvals, settled runs, finished setups.",
8428
+ description: "Lists pending questions and approvals (always), and the runs this client started and setups that finished since --cursor (default: the last hour). --follow --jsonl polls and prints each new item once, which is how a voice runtime or notifier can hear about work without asking; it stops at --timeout or Ctrl+C and never changes anything.",
8429
+ positionals: [],
8430
+ options: [
8431
+ {
8432
+ name: "cursor",
8433
+ kind: "string",
8434
+ valueName: "CURSOR",
8435
+ description: "cursor from a previous read: only what changed since."
8436
+ },
8437
+ { name: "limit", kind: "integer", valueName: "N", description: "Most items (max 50)." },
8438
+ {
8439
+ name: "follow",
8440
+ kind: "boolean",
8441
+ description: "Keep polling and print new items (use --jsonl for machines)."
8442
+ },
8443
+ TIMEOUT_OPTION
8444
+ ],
8445
+ jsonl: true,
8446
+ async run(context, parsed) {
8447
+ const session = await context.session();
8448
+ const input = {};
8449
+ if (parsed.options.cursor !== undefined)
8450
+ input.cursor = parsed.options.cursor;
8451
+ if (parsed.options.limit !== undefined)
8452
+ input.limit = parsed.options.limit;
8453
+ if (parsed.options.follow !== true) {
8454
+ if (context.global.output === "jsonl") {
8455
+ throw new CliError("invalid-input", "--jsonl needs --follow");
8456
+ }
8457
+ const page = await session.read("attention.list", input);
8458
+ return {
8459
+ action: "attention.list",
8460
+ result: page,
8461
+ connection: session.identity,
8462
+ human: [
8463
+ ...page.items.length ? page.items.map(attentionLine) : ["Nothing needs attention."],
8464
+ ...page.more ? [`and ${page.more} more (raise --limit)`] : [],
8465
+ `cursor: ${page.cursor}`
8466
+ ]
8467
+ };
8468
+ }
8469
+ const seen = new Set;
8470
+ let cursor = input.cursor;
8471
+ let emitted = 0;
8472
+ try {
8473
+ await observe(async () => {
8474
+ const page = await session.read("attention.list", {
8475
+ ...input,
8476
+ ...cursor ? { cursor } : {}
8477
+ });
8478
+ cursor = page.cursor;
8479
+ for (const item of page.items) {
8480
+ const key = itemKey(item);
8481
+ if (seen.has(key))
8482
+ continue;
8483
+ seen.add(key);
8484
+ emitted += 1;
8485
+ if (context.global.output === "jsonl") {
8486
+ context.io.stdout(`${JSON.stringify({ type: "attention", item })}
8487
+ `);
8488
+ } else if (context.global.output === "human") {
8489
+ context.io.stdout(`${attentionLine(item)}
8490
+ `);
8491
+ }
8492
+ }
8493
+ if (seen.size > 1e4)
8494
+ seen.clear();
8495
+ return { done: false };
8496
+ }, {
8497
+ timeoutMs: parsed.options.timeout ?? DEFAULT_WAIT_MS,
8498
+ signal: context.signal
8499
+ });
8500
+ } catch (error) {
8501
+ if (!(error instanceof ObservationStopped))
8502
+ throw error;
8503
+ }
8504
+ return {
8505
+ action: "attention.list",
8506
+ result: { followed: true, emitted, cursor: cursor ?? null },
8507
+ connection: session.identity,
8508
+ human: []
8509
+ };
8510
+ }
8511
+ }
8512
+ ];
8513
+
8514
+ // ../protocol/src/public-api-resources.ts
8515
+ var PUBLIC_CHANGE_SCOPES = Object.freeze(["environment"]);
8516
+ var AGENT_PLATFORM_KEYS = ["claude", "codex", "cursor", "grok", "opencode", "pi"];
8517
+ function agentSettingDescriptors(scope) {
8518
+ const application = "next-session";
8519
+ return [
8520
+ {
8521
+ key: "agent.defaultAgent",
8522
+ scope,
8523
+ type: "agent",
8524
+ inherits: true,
8525
+ application,
8526
+ description: "Agent platform used when a launch does not name one."
8527
+ },
8528
+ ...AGENT_PLATFORM_KEYS.flatMap((platform) => [
8529
+ {
8530
+ key: `agent.${platform}.mode`,
8531
+ scope,
8532
+ type: "agent-mode",
8533
+ inherits: true,
8534
+ application,
8535
+ description: `Launch mode (native or terminal) for ${platform}.`
8536
+ },
8537
+ {
8538
+ key: `agent.${platform}.model`,
8539
+ scope,
8540
+ type: "string",
8541
+ inherits: true,
8542
+ application,
8543
+ description: `Default model for new ${platform} sessions.`
8544
+ },
8545
+ {
8546
+ key: `agent.${platform}.reasoningEffort`,
8547
+ scope,
8548
+ type: "string",
8549
+ inherits: true,
8550
+ application,
8551
+ description: `Default reasoning level for new ${platform} sessions.`
8552
+ },
8553
+ {
8554
+ key: `agent.${platform}.fastMode`,
8555
+ scope,
8556
+ type: "boolean",
8557
+ inherits: true,
8558
+ application,
8559
+ description: `Default speed for new ${platform} sessions (true is Fast).`
8560
+ }
8561
+ ])
8562
+ ];
8563
+ }
8564
+ var PUBLIC_PROJECT_SETTINGS = Object.freeze([
8565
+ {
8566
+ key: "defaultBranch",
8567
+ scope: "project",
8568
+ type: "string",
8569
+ inherits: false,
8570
+ application: "next-environment",
8571
+ description: "Branch new environments are created from."
8572
+ },
8573
+ {
8574
+ key: "prBaseBranch",
8575
+ scope: "project",
8576
+ type: "string",
8577
+ inherits: false,
8578
+ application: "applied",
8579
+ description: "Base branch for pull requests and diff baselines."
8580
+ },
8581
+ {
8582
+ key: "defaultPortMappings",
8583
+ scope: "project",
8584
+ type: "port-mappings",
8585
+ inherits: false,
8586
+ environmentTypes: ["container"],
8587
+ application: "next-environment",
8588
+ description: "Port mappings copied into new container environments."
8589
+ },
8590
+ {
8591
+ key: "filesToCopy",
8592
+ scope: "project",
8593
+ type: "string-list",
8594
+ inherits: false,
8595
+ application: "next-environment",
8596
+ description: "Untracked files copied into new environments."
8597
+ },
8598
+ {
8599
+ key: "entryPort",
8600
+ scope: "project",
8601
+ type: "port",
8602
+ inherits: false,
8603
+ application: "next-environment",
8604
+ description: "Default preview entry port for new environments."
8605
+ },
8606
+ ...agentSettingDescriptors("project")
8607
+ ]);
8608
+ var PUBLIC_ENVIRONMENT_SETTINGS = Object.freeze([
8609
+ {
8610
+ key: "portMappings",
8611
+ scope: "environment",
8612
+ type: "port-mappings",
8613
+ inherits: false,
8614
+ environmentTypes: ["container"],
8615
+ application: "next-start",
8616
+ description: "Container port mappings; a running container adopts them on recreate."
8617
+ },
8618
+ {
8619
+ key: "allowedDomains",
8620
+ scope: "environment",
8621
+ type: "domain-list",
8622
+ inherits: true,
8623
+ environmentTypes: ["container"],
8624
+ application: "next-start",
8625
+ description: "Restricted-network allowlist; unset inherits the global list. A running container whose image supports it applies the change immediately (reported as applied)."
8626
+ },
8627
+ ...agentSettingDescriptors("environment")
8628
+ ]);
8629
+
8630
+ // src/client/format.ts
8631
+ function table(headers, rows) {
8632
+ if (rows.length === 0)
8633
+ return ["(none)"];
8634
+ const widths = headers.map((header, index) => Math.min(60, Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length))));
8635
+ const line = (cells) => cells.map((cell, index) => {
8636
+ const text = cell.length > widths[index] ? `${cell.slice(0, widths[index] - 1)}\u2026` : cell;
8637
+ return text.padEnd(widths[index]);
8638
+ }).join(" ").trimEnd();
8639
+ return [line(headers), ...rows.map(line)];
8640
+ }
8641
+ function keyValues(entries) {
8642
+ const width = Math.max(...entries.map(([key]) => key.length));
8643
+ return entries.filter(([, value]) => value !== undefined).map(([key, value]) => {
8644
+ const text = value === null ? "-" : typeof value === "object" ? JSON.stringify(value) : String(value);
8645
+ return `${key.padEnd(width)} ${text}`;
8646
+ });
8647
+ }
8648
+ function projectLines(project) {
8649
+ return keyValues([
8650
+ ["id", project.id],
8651
+ ["name", project.name],
8652
+ ["remote", project.gitUrl],
8653
+ ["path", project.localPath],
8654
+ ["folder", project.folder],
8655
+ ["environments", project.environmentCount],
8656
+ ["revision", project.revision]
8657
+ ]);
8658
+ }
8659
+ function projectRows(projects) {
8660
+ return table(["ID", "NAME", "ENVS", "PATH"], projects.map((project) => [
8661
+ project.id,
8662
+ project.name,
8663
+ String(project.environmentCount),
8664
+ project.localPath ?? "-"
8665
+ ]));
8666
+ }
8667
+ function environmentLines(environment) {
8668
+ return keyValues([
8669
+ ["id", environment.id],
8670
+ ["project", environment.projectId],
8671
+ ["name", environment.name],
8672
+ ["branch", environment.branch],
8673
+ ["type", environment.environmentType],
8674
+ ["status", environment.status],
8675
+ ["ready", environment.ready],
8676
+ ["setup", `${environment.setup.phase}${environment.setup.overridden ? " (overridden)" : ""}`],
8677
+ ["activity", environment.activity.state],
8678
+ ["lifecycle error", environment.lifecycle.error],
8679
+ ["workspace", environment.workspacePath],
8680
+ ["base", environment.base.commit ?? environment.base.branch],
8681
+ ["startup session", environment.startupSession?.sessionId]
8682
+ ]);
8683
+ }
8684
+ function environmentRows(environments) {
8685
+ return table(["ID", "NAME", "TYPE", "STATUS", "READY"], environments.map((environment) => [
8686
+ environment.id,
8687
+ environment.name,
8688
+ environment.environmentType,
8689
+ environment.status,
8690
+ environment.ready ? "yes" : "no"
8691
+ ]));
8692
+ }
8693
+ function sessionRows(sessions) {
8694
+ return table(["SESSION", "AGENT", "TAB", "ACTIVITY", "TITLE"], sessions.map((session) => [
8695
+ session.id,
8696
+ session.agent,
8697
+ session.tabId,
8698
+ session.activity,
8699
+ session.title ?? ""
8700
+ ]));
8701
+ }
8702
+ function sessionLines(session) {
8703
+ return keyValues([
8704
+ ["id", session.id],
8705
+ ["environment", session.environmentId],
8706
+ ["tab", session.tabId],
8707
+ ["agent", session.agent],
8708
+ ["title", session.title],
8709
+ ["activity", session.activity],
8710
+ ["latest request", session.latestRequestId],
8711
+ ["pending interactions", session.pendingInteractionCount],
8712
+ ["recoverable dispatch", session.recoverableDispatch]
8713
+ ]);
8714
+ }
8715
+ function receiptLines(receipt) {
8716
+ return keyValues([
8717
+ ["operation", receipt.operationId],
8718
+ ["action", receipt.action],
8719
+ ["state", receipt.state],
8720
+ ["stage", receipt.stage],
8721
+ ["request", `${receipt.requestId} (${receipt.namespace})`],
8722
+ ["replayed", receipt.replayed],
8723
+ ["resources", receipt.resources],
8724
+ ["dispatch", receipt.dispatch?.state],
8725
+ ["execution", receipt.execution?.state],
8726
+ ["evidence", receipt.execution?.evidence],
8727
+ ["exit code", receipt.execution?.exitCode],
8728
+ ["error", receipt.error ? `${receipt.error.code}: ${receipt.error.message}` : undefined],
8729
+ ["updated", receipt.updatedAt],
8730
+ ["retained until", receipt.retainedUntil]
8731
+ ]);
8732
+ }
8733
+ function settingsLines(snapshot) {
8734
+ return [
8735
+ `revision ${snapshot.revision}`,
8736
+ ...table(["KEY", "VALUE", "EFFECTIVE", "SOURCE", "APPLIES"], snapshot.settings.map((setting) => [
8737
+ setting.key,
8738
+ setting.value === null || setting.value === undefined ? "-" : JSON.stringify(setting.value),
8739
+ setting.effective === null || setting.effective === undefined ? "-" : JSON.stringify(setting.effective),
8740
+ setting.source,
8741
+ setting.application
8742
+ ]))
8743
+ ];
8523
8744
  }
8524
8745
 
8525
8746
  // src/client/commands/changes.ts
@@ -8625,6 +8846,7 @@ function changesHistoryLines(history) {
8625
8846
  var changesCommands = [
8626
8847
  {
8627
8848
  path: ["changes", "view"],
8849
+ action: "changes.view",
8628
8850
  summary: "Show an environment's file and line changes as the backend measures them.",
8629
8851
  description: "Read-only: prints the backend's change projection for the environment \u2014 the numbers the desktop shows \u2014 and one page of changed files. Counts carry their quality: ~ partial or estimated, ? unknown, \u2265/\u2264 a bound; binary files and submodules have no line counts. Nothing is ever shown as zero unless the backend measured zero. Pass the printed cursor as --cursor for the next page; a cursor stops working once the changes move on. --json prints the action result verbatim ({projection, files}).",
8630
8852
  positionals: [{ name: "environment", required: true, description: "Environment ID." }],
@@ -8674,6 +8896,7 @@ var changesCommands = [
8674
8896
  },
8675
8897
  {
8676
8898
  path: ["changes", "history"],
8899
+ action: "changes.history",
8677
8900
  summary: "List a session's turns, oldest first, with the changes each one made.",
8678
8901
  description: "Read-only: each turn's backend measurement from its start to its end, with the same count qualities as `changes view`. No workspace scan, transcript or provider read. Pass the printed cursor as --cursor for newer turns.",
8679
8902
  positionals: [{ name: "session", required: true, description: "Session ID (ses_\u2026)." }],
@@ -9900,6 +10123,7 @@ function settingsCommands(scope) {
9900
10123
  return [
9901
10124
  {
9902
10125
  path: [scope, "config", "get"],
10126
+ action: `${scope}.config.get`,
9903
10127
  summary: `Show ${scope} settings with effective values, sources and application timing.`,
9904
10128
  positionals: [{ name: idName, required: true }],
9905
10129
  options: [],
@@ -9918,6 +10142,7 @@ function settingsCommands(scope) {
9918
10142
  },
9919
10143
  {
9920
10144
  path: [scope, "config", "set"],
10145
+ action: `${scope}.config.set`,
9921
10146
  summary: `Set and/or unset ${scope} settings atomically.`,
9922
10147
  description: `Settable keys:
9923
10148
  ${keysHelp}
@@ -10015,6 +10240,8 @@ ${applies}`,
10015
10240
  },
10016
10241
  {
10017
10242
  path: [scope, "config", "unset"],
10243
+ action: `${scope}.config.set`,
10244
+ inputFields: { key: "unset" },
10018
10245
  summary: `Unset ${scope} settings, restoring inheritance.`,
10019
10246
  positionals: [
10020
10247
  { name: idName, required: true },
@@ -10092,6 +10319,7 @@ async function lifecycle(context, parsed, action, conditions, extraInput = {}) {
10092
10319
  var environmentCommands = [
10093
10320
  {
10094
10321
  path: ["environment", "list"],
10322
+ action: "environment.list",
10095
10323
  summary: "List environments from the backend snapshot.",
10096
10324
  positionals: [],
10097
10325
  options: [
@@ -10138,6 +10366,7 @@ var environmentCommands = [
10138
10366
  },
10139
10367
  {
10140
10368
  path: ["environment", "get"],
10369
+ action: "environment.get",
10141
10370
  summary: "Show one environment by ID, or by --project and --name when unique.",
10142
10371
  positionals: [{ name: "environment", required: false }],
10143
10372
  options: [
@@ -10173,6 +10402,7 @@ var environmentCommands = [
10173
10402
  },
10174
10403
  {
10175
10404
  path: ["environment", "create"],
10405
+ action: "environment.create",
10176
10406
  summary: "Create an environment record (does not start it).",
10177
10407
  description: "--base-branch with --base-commit (a full 40-character SHA) pins the exact base; the backend validates it and records what was used. Without them the project's default branch is used at start time.",
10178
10408
  positionals: [],
@@ -10231,6 +10461,7 @@ var environmentCommands = [
10231
10461
  },
10232
10462
  {
10233
10463
  path: ["environment", "start"],
10464
+ action: "environment.start",
10234
10465
  summary: "Start an environment; setup continues in the backend.",
10235
10466
  description: "`--wait running` returns once processes run; `--wait ready` also requires setup to complete (an explicit setup override is reported, never applied by the CLI). Setup failure exits 1 with the recorded reason.",
10236
10467
  positionals: [{ name: "environment", required: true }],
@@ -10240,6 +10471,7 @@ var environmentCommands = [
10240
10471
  },
10241
10472
  {
10242
10473
  path: ["environment", "stop"],
10474
+ action: "environment.stop",
10243
10475
  summary: "Stop an environment's processes (and container).",
10244
10476
  positionals: [{ name: "environment", required: true }],
10245
10477
  options: [waitOption(["stopped"]), TIMEOUT_OPTION, REQUEST_ID_OPTION],
@@ -10248,6 +10480,7 @@ var environmentCommands = [
10248
10480
  },
10249
10481
  {
10250
10482
  path: ["environment", "recreate"],
10483
+ action: "environment.recreate",
10251
10484
  summary: "Rebuild the container, keeping its files; with --discard, DESTRUCTIVELY reset it (container environments only).",
10252
10485
  description: "Without --discard the workspace (tracked, untracked and ignored files, unpushed commits) and the preserved agent session state are copied to a new container and verified before it replaces the old one, which is kept stopped as a recovery copy. The rebuild is refused, with nothing changed, when the image or Docker engine cannot preserve them. --discard instead deletes the container's local files, installed tools and container-local agent sessions. Local worktree environments cannot be recreated.",
10253
10486
  positionals: [{ name: "environment", required: true }],
@@ -10266,6 +10499,7 @@ var environmentCommands = [
10266
10499
  },
10267
10500
  {
10268
10501
  path: ["environment", "delete"],
10502
+ action: "environment.delete",
10269
10503
  summary: "DESTRUCTIVE: stop and delete the environment, its worktree or container, and its record.",
10270
10504
  description: "Uncommitted work in the environment is lost. Cleanup is tracked by the operation; a failed cleanup stays inspectable and can be retried with a new request.",
10271
10505
  positionals: [{ name: "environment", required: true }],
@@ -10275,6 +10509,7 @@ var environmentCommands = [
10275
10509
  },
10276
10510
  {
10277
10511
  path: ["environment", "fork"],
10512
+ action: "environment.fork",
10278
10513
  summary: "Create a new environment from this environment's current state.",
10279
10514
  positionals: [{ name: "environment", required: true }],
10280
10515
  options: [TYPE_OPTION, REQUEST_ID_OPTION],
@@ -10295,6 +10530,7 @@ var environmentCommands = [
10295
10530
  },
10296
10531
  {
10297
10532
  path: ["environment", "rename"],
10533
+ action: "environment.rename",
10298
10534
  summary: "Rename an environment (and its branch).",
10299
10535
  positionals: [
10300
10536
  { name: "environment", required: true },
@@ -10317,6 +10553,7 @@ var environmentCommands = [
10317
10553
  },
10318
10554
  {
10319
10555
  path: ["environment", "launch"],
10556
+ action: "environment.launch",
10320
10557
  summary: "Create and start an environment whose startup agent receives one first prompt.",
10321
10558
  description: "The backend owns the whole flow: create, start, setup, then exactly one initial prompt to the startup session. Do not follow it with `session start`. --wait ready observes setup; follow the prompt's run with `run wait`.",
10322
10559
  positionals: [],
@@ -10427,6 +10664,8 @@ var environmentCommands = [
10427
10664
  },
10428
10665
  {
10429
10666
  path: ["environment", "exec"],
10667
+ action: "environment.exec",
10668
+ inputFields: { "stdin-file": "stdinBase64", "exec-timeout": "timeoutMs" },
10430
10669
  summary: "Run one command in the environment workspace and report its exit status.",
10431
10670
  description: "Arguments after `--` are passed as argv without a shell; use `-- sh -c '...'` to opt into shell expansion. The command runs in a backend-owned worker that outlives this client; --wait observes it and exits with the operation's result (`--exit-code` passes the child's exit code through instead). Output is kept in bounded private artifacts; read it with `run output`.",
10432
10671
  positionals: [{ name: "environment", required: true }],
@@ -10646,6 +10885,7 @@ async function waitForReceipt(context, session, receipt, limit, action) {
10646
10885
  function taskCommand(spec) {
10647
10886
  return {
10648
10887
  path: spec.path,
10888
+ action: spec.action,
10649
10889
  summary: spec.summary,
10650
10890
  ...spec.description ? { description: spec.description } : {},
10651
10891
  positionals: [],
@@ -10677,6 +10917,7 @@ function taskCommand(spec) {
10677
10917
  var hostCommands = [
10678
10918
  {
10679
10919
  path: ["host", "status"],
10920
+ action: "host.status",
10680
10921
  summary: "Show the Orkestrator version on the backend's computer, its updater and any update.",
10681
10922
  positionals: [],
10682
10923
  options: [],
@@ -10693,6 +10934,7 @@ var hostCommands = [
10693
10934
  },
10694
10935
  {
10695
10936
  path: ["host", "update", "channel"],
10937
+ action: "host.update.channel",
10696
10938
  summary: "Choose the host's update channel: stable, beta or unstable.",
10697
10939
  positionals: [{ name: "channel", required: true, description: "stable, beta or unstable" }],
10698
10940
  options: [REQUEST_ID_OPTION],
@@ -10713,6 +10955,7 @@ var hostCommands = [
10713
10955
  },
10714
10956
  {
10715
10957
  path: ["host", "update", "check"],
10958
+ action: "host.update.check",
10716
10959
  summary: "Ask the backend's computer to check for an Orkestrator update.",
10717
10960
  positionals: [],
10718
10961
  options: [WAIT_OPTION, TIMEOUT_OPTION, REQUEST_ID_OPTION],
@@ -10747,6 +10990,13 @@ var hostCommands = [
10747
10990
  },
10748
10991
  {
10749
10992
  path: ["host", "update", "apply"],
10993
+ action: "host.update.apply",
10994
+ inputFields: {
10995
+ release: "version",
10996
+ yes: "confirm",
10997
+ "when-idle": "drain",
10998
+ "max-wait": "maxWaitSeconds"
10999
+ },
10750
11000
  summary: "Install an Orkestrator update on the backend's computer. It restarts there after a 60 s countdown.",
10751
11001
  description: "Someone at that computer can postpone the restart; the update then installs the next time the app quits. Restarting ends agent turns and terminals running on that computer; containers keep running.",
10752
11002
  positionals: [],
@@ -10820,6 +11070,7 @@ var hostCommands = [
10820
11070
  },
10821
11071
  {
10822
11072
  path: ["host", "update", "cancel"],
11073
+ action: "host.update.cancel",
10823
11074
  summary: "Cancel the host's update before it restarts.",
10824
11075
  positionals: [],
10825
11076
  options: [REQUEST_ID_OPTION],
@@ -10862,6 +11113,98 @@ var hostCommands = [
10862
11113
  })
10863
11114
  ];
10864
11115
 
11116
+ // src/client/commands/mail.ts
11117
+ var mailCommands = [
11118
+ {
11119
+ path: ["mail", "mailboxes"],
11120
+ action: "mail.mailboxes",
11121
+ summary: "List agent mailboxes a message can be sent to.",
11122
+ positionals: [],
11123
+ options: [
11124
+ { name: "q", kind: "string", valueName: "TEXT", description: "Filter by name." },
11125
+ { name: "offset", kind: "integer", valueName: "N", description: "Rows to skip." },
11126
+ { name: "limit", kind: "integer", valueName: "N", description: "Rows (max 200)." }
11127
+ ],
11128
+ async run(context, parsed) {
11129
+ const session = await context.session();
11130
+ const page = await session.read("mail.mailboxes", {
11131
+ ...parsed.options.q !== undefined ? { q: parsed.options.q } : {},
11132
+ ...parsed.options.offset !== undefined ? { offset: parsed.options.offset } : {},
11133
+ ...parsed.options.limit !== undefined ? { limit: parsed.options.limit } : {}
11134
+ });
11135
+ return {
11136
+ action: "mail.mailboxes",
11137
+ result: page,
11138
+ connection: session.identity,
11139
+ human: table(["ENVIRONMENT", "TAB", "NAME", "PRESENCE"], page.mailboxes.map((mailbox) => [
11140
+ mailbox.environmentId,
11141
+ mailbox.tabId,
11142
+ `${mailbox.environmentName} / ${mailbox.displayName}`,
11143
+ mailbox.presence
11144
+ ]))
11145
+ };
11146
+ }
11147
+ },
11148
+ {
11149
+ path: ["mail", "send"],
11150
+ action: "mail.send",
11151
+ summary: "Place a message in one tab's inbox (never injected into a turn).",
11152
+ positionals: [
11153
+ { name: "environment", required: true },
11154
+ { name: "tab", required: true }
11155
+ ],
11156
+ options: [
11157
+ { name: "subject", kind: "string", valueName: "TEXT", description: "Subject line." },
11158
+ { name: "body", kind: "string", valueName: "TEXT", description: "Message text." },
11159
+ {
11160
+ name: "body-file",
11161
+ kind: "string",
11162
+ valueName: "PATH",
11163
+ description: "Read the message from a local file."
11164
+ },
11165
+ { name: "body-stdin", kind: "boolean", description: "Read the message from stdin." },
11166
+ REQUEST_ID_OPTION
11167
+ ],
11168
+ inputFields: {
11169
+ environment: "toEnvironmentId",
11170
+ tab: "toTabId",
11171
+ "body-file": "body",
11172
+ "body-stdin": "body"
11173
+ },
11174
+ idOutput: "message ID",
11175
+ async run(context, parsed) {
11176
+ const body = await readTextSource(context.io, {
11177
+ file: parsed.options["body-file"],
11178
+ stdin: parsed.options["body-stdin"],
11179
+ inline: parsed.options.body,
11180
+ label: "Message",
11181
+ maxBytes: 32 * 1024,
11182
+ names: { file: "--body-file", stdin: "--body-stdin", inline: "--body" },
11183
+ required: true
11184
+ });
11185
+ const subject = stringOption(parsed.options, "subject");
11186
+ if (subject !== undefined && subject.trim().length === 0) {
11187
+ throw new CliError("invalid-input", "--subject is empty");
11188
+ }
11189
+ const { session, result, receipt, warnings } = await submit(context, "mail.send", {
11190
+ toEnvironmentId: parsed.positionals.environment,
11191
+ toTabId: parsed.positionals.tab,
11192
+ body,
11193
+ ...subject ? { subject } : {}
11194
+ }, parsed.options);
11195
+ return {
11196
+ action: "mail.send",
11197
+ result,
11198
+ receipt,
11199
+ warnings,
11200
+ connection: session.identity,
11201
+ ids: result.messageId ? [result.messageId] : [],
11202
+ human: [`sent message ${result.messageId ?? ""}`.trim()]
11203
+ };
11204
+ }
11205
+ }
11206
+ ];
11207
+
10865
11208
  // src/client/commands/service.ts
10866
11209
  import { spawnSync } from "child_process";
10867
11210
  import { existsSync as existsSync5 } from "fs";
@@ -11477,6 +11820,7 @@ var PAGE_OPTIONS = [
11477
11820
  var projectCommands = [
11478
11821
  {
11479
11822
  path: ["project", "list"],
11823
+ action: "project.list",
11480
11824
  summary: "List projects.",
11481
11825
  positionals: [],
11482
11826
  options: PAGE_OPTIONS,
@@ -11501,6 +11845,7 @@ var projectCommands = [
11501
11845
  },
11502
11846
  {
11503
11847
  path: ["project", "get"],
11848
+ action: "project.get",
11504
11849
  summary: "Show one project by ID, or by --name when unique.",
11505
11850
  positionals: [{ name: "project", required: false }],
11506
11851
  options: [
@@ -11526,6 +11871,7 @@ var projectCommands = [
11526
11871
  },
11527
11872
  {
11528
11873
  path: ["project", "add"],
11874
+ action: "project.add",
11529
11875
  summary: "Register a repository, attach a backend checkout, or clone into a backend path.",
11530
11876
  description: "--remote alone registers the remote. --path alone attaches an existing checkout on the BACKEND's filesystem and reads its origin there. Both together attach the checkout if it exists, or clone the remote into that backend path when it is missing or empty. No GitHub repository is created.",
11531
11877
  positionals: [],
@@ -11560,6 +11906,7 @@ var projectCommands = [
11560
11906
  },
11561
11907
  {
11562
11908
  path: ["project", "create"],
11909
+ action: "project.create",
11563
11910
  summary: "Create a new repository: git init, a PRIVATE GitHub repository, and a push.",
11564
11911
  description: "This creates an external repository on GitHub using the backend's GitHub CLI credentials, pushes the initial commit, and registers the project. It requires --github-private to confirm that effect. Local-only initialization is not supported.",
11565
11912
  positionals: [],
@@ -11599,6 +11946,15 @@ var projectCommands = [
11599
11946
  },
11600
11947
  {
11601
11948
  path: ["project", "update"],
11949
+ action: "project.update",
11950
+ inputFields: {
11951
+ name: "set",
11952
+ folder: "set",
11953
+ "no-folder": "set",
11954
+ remote: "set",
11955
+ path: "set",
11956
+ "no-path": "set"
11957
+ },
11602
11958
  summary: "Edit project metadata (does not move checkouts or rewrite .git/config).",
11603
11959
  positionals: [{ name: "project", required: true }],
11604
11960
  options: [
@@ -11668,6 +12024,7 @@ var projectCommands = [
11668
12024
  },
11669
12025
  {
11670
12026
  path: ["project", "remove"],
12027
+ action: "project.remove",
11671
12028
  summary: "Remove an empty project registration (refused while environments exist).",
11672
12029
  description: "Only the registration is removed. The checkout on disk and any remote repository are never deleted.",
11673
12030
  positionals: [{ name: "project", required: true }],
@@ -11693,6 +12050,7 @@ var projectCommands = [
11693
12050
  var runCommands = [
11694
12051
  {
11695
12052
  path: ["run", "get"],
12053
+ action: "run.get",
11696
12054
  summary: "Show an operation by ID, or by its original --request-id.",
11697
12055
  description: "Lookup by request key finds the operation even when the original response was lost. `history-expired` (exit 3) means the key's namespace was retired and its outcome is no longer retained; it is not evidence that nothing ran.",
11698
12056
  positionals: [{ name: "operation", required: false }],
@@ -11749,6 +12107,7 @@ var runCommands = [
11749
12107
  },
11750
12108
  {
11751
12109
  path: ["run", "wait"],
12110
+ action: "run.get",
11752
12111
  summary: "Observe an operation until it finishes (exit 0 only on success).",
11753
12112
  description: "Exit codes: 0 succeeded, 1 failed/cancelled/interrupted, 5 --timeout passed (still running), 6 waiting for an interaction answer, 7 dispatch or outcome unknown. Ctrl+C stops observing only.",
11754
12113
  positionals: [{ name: "operation", required: true }],
@@ -11779,6 +12138,7 @@ var runCommands = [
11779
12138
  },
11780
12139
  {
11781
12140
  path: ["run", "retry"],
12141
+ action: "run.retry",
11782
12142
  summary: "Retry the exact parked prompt/steer of an unknown dispatch under its original key.",
11783
12143
  description: "Never sends a different prompt and never mints a new key.",
11784
12144
  positionals: [{ name: "operation", required: true }],
@@ -11799,6 +12159,7 @@ var runCommands = [
11799
12159
  },
11800
12160
  {
11801
12161
  path: ["run", "discard"],
12162
+ action: "run.discard",
11802
12163
  summary: "Clear the recovery state of an unknown dispatch (does NOT undo a turn that ran).",
11803
12164
  positionals: [{ name: "operation", required: true }],
11804
12165
  options: [REQUEST_ID_OPTION],
@@ -11818,6 +12179,7 @@ var runCommands = [
11818
12179
  },
11819
12180
  {
11820
12181
  path: ["run", "cancel"],
12182
+ action: "run.cancel",
11821
12183
  summary: "Cancel a running exec operation (its exact worker and descendants).",
11822
12184
  positionals: [{ name: "operation", required: true }],
11823
12185
  options: [REQUEST_ID_OPTION],
@@ -11837,6 +12199,8 @@ var runCommands = [
11837
12199
  },
11838
12200
  {
11839
12201
  path: ["run", "output"],
12202
+ action: "run.output",
12203
+ inputFields: { tail: "tailBytes" },
11840
12204
  summary: "Read a bounded window of an exec operation's stdout or stderr.",
11841
12205
  positionals: [{ name: "operation", required: true }],
11842
12206
  options: [
@@ -12039,6 +12403,7 @@ function parseAnswers(options2) {
12039
12403
  var sessionCommands = [
12040
12404
  {
12041
12405
  path: ["agent", "options"],
12406
+ action: "agent.options",
12042
12407
  summary: "Enabled agents, model catalogues (with freshness), and supported controls.",
12043
12408
  description: "Reads the backend's cached catalogues; --refresh asks providers again, which can take a while. An `empty` catalogue is a successful empty answer; `stale`/`unavailable` means the backend could not read it.",
12044
12409
  positionals: [],
@@ -12084,6 +12449,7 @@ var sessionCommands = [
12084
12449
  },
12085
12450
  {
12086
12451
  path: ["session", "list"],
12452
+ action: "session.list",
12087
12453
  summary: "List native-agent sessions of an environment (no transcripts are read).",
12088
12454
  positionals: [],
12089
12455
  options: [
@@ -12112,6 +12478,7 @@ var sessionCommands = [
12112
12478
  },
12113
12479
  {
12114
12480
  path: ["session", "get"],
12481
+ action: "session.get",
12115
12482
  summary: "Show one session (activity, latest request, pending interactions).",
12116
12483
  positionals: [{ name: "session", required: true }],
12117
12484
  options: [],
@@ -12132,6 +12499,7 @@ var sessionCommands = [
12132
12499
  },
12133
12500
  {
12134
12501
  path: ["session", "start"],
12502
+ action: "session.start",
12135
12503
  summary: "Start an independent conversation in a ready environment and send its first prompt.",
12136
12504
  description: "Creates a new agent tab without focusing it. The response is a submission receipt; add --wait (or run `run wait`) to observe the turn. Independent sessions in one environment share its files.",
12137
12505
  positionals: [],
@@ -12194,6 +12562,7 @@ var sessionCommands = [
12194
12562
  },
12195
12563
  {
12196
12564
  path: ["session", "prompt"],
12565
+ action: "session.prompt",
12197
12566
  summary: "Send a follow-up prompt to an idle session.",
12198
12567
  description: "Busy sessions are refused (exit 8); use `session steer` to redirect an active turn. The target must be the exact session: a vanished session is never replaced by a new one.",
12199
12568
  positionals: [{ name: "session", required: true }],
@@ -12232,6 +12601,7 @@ var sessionCommands = [
12232
12601
  },
12233
12602
  {
12234
12603
  path: ["session", "stop"],
12604
+ action: "session.stop",
12235
12605
  summary: "Stop the session's current turn (not the environment).",
12236
12606
  description: "--expect-run binds the stop to that run: if a different turn is now active the stop is refused (exit 8). Success requires the provider to confirm the turn ended; otherwise the result is unknown (exit 7).",
12237
12607
  positionals: [{ name: "session", required: true }],
@@ -12263,6 +12633,7 @@ var sessionCommands = [
12263
12633
  },
12264
12634
  {
12265
12635
  path: ["session", "steer"],
12636
+ action: "session.steer",
12266
12637
  summary: "Redirect the active turn (providers that support steering only).",
12267
12638
  positionals: [{ name: "session", required: true }],
12268
12639
  options: [
@@ -12311,6 +12682,7 @@ var sessionCommands = [
12311
12682
  },
12312
12683
  {
12313
12684
  path: ["session", "config", "get"],
12685
+ action: "session.config.get",
12314
12686
  summary: "Show the live session's effective controls.",
12315
12687
  positionals: [{ name: "session", required: true }],
12316
12688
  options: [],
@@ -12329,6 +12701,7 @@ var sessionCommands = [
12329
12701
  },
12330
12702
  {
12331
12703
  path: ["session", "config", "set"],
12704
+ action: "session.config.set",
12332
12705
  summary: "Change the live session's controls (not environment defaults).",
12333
12706
  description: "Applies to this conversation now, as validated by its provider; the response shows the values actually in force (a provider may clamp them). To change defaults for future sessions use `environment config set`.",
12334
12707
  positionals: [{ name: "session", required: true }],
@@ -12350,6 +12723,7 @@ var sessionCommands = [
12350
12723
  },
12351
12724
  {
12352
12725
  path: ["session", "history"],
12726
+ action: "session.history",
12353
12727
  summary: "List earlier provider conversations this session can resume.",
12354
12728
  positionals: [{ name: "session", required: true }],
12355
12729
  options: [],
@@ -12372,6 +12746,7 @@ var sessionCommands = [
12372
12746
  },
12373
12747
  {
12374
12748
  path: ["session", "resume"],
12749
+ action: "session.resume",
12375
12750
  summary: "Rebind the session to an earlier conversation from `session history`.",
12376
12751
  description: "The current conversation's history is kept; nothing is deleted.",
12377
12752
  positionals: [
@@ -12386,6 +12761,8 @@ var sessionCommands = [
12386
12761
  },
12387
12762
  {
12388
12763
  path: ["session", "fork"],
12764
+ action: "session.fork",
12765
+ inputFields: { message: "messageId" },
12389
12766
  summary: "Fork the conversation into a new session (optionally at a message).",
12390
12767
  positionals: [{ name: "session", required: true }],
12391
12768
  options: [
@@ -12416,6 +12793,7 @@ var sessionCommands = [
12416
12793
  },
12417
12794
  {
12418
12795
  path: ["session", "interactions", "list"],
12796
+ action: "session.interactions",
12419
12797
  summary: "List pending questions and approvals with their permitted answers.",
12420
12798
  positionals: [{ name: "session", required: true }],
12421
12799
  options: [],
@@ -12440,6 +12818,8 @@ var sessionCommands = [
12440
12818
  },
12441
12819
  {
12442
12820
  path: ["session", "interactions", "resolve"],
12821
+ action: "session.interaction.resolve",
12822
+ inputFields: { revision: "expectedRevision", choose: "answers", text: "answers" },
12443
12823
  summary: "Answer one pending interaction at its current revision.",
12444
12824
  description: "--revision must match the interaction's current revision; a replaced or expired interaction is refused (exit 8), never answered. Nothing is approved implicitly.",
12445
12825
  positionals: [
@@ -12511,6 +12891,7 @@ var sessionCommands = [
12511
12891
  },
12512
12892
  {
12513
12893
  path: ["session", "transcript"],
12894
+ action: "session.transcript",
12514
12895
  summary: "Read a bounded transcript page; --follow polls for new messages.",
12515
12896
  description: "Pages are oldest-first and hold at most --limit messages (max 100). Pass the returned olderCursor as --before for earlier history. --follow --jsonl streams one record per new message by polling snapshots; it stops at --timeout or Ctrl+C and never affects the session.",
12516
12897
  positionals: [{ name: "session", required: true }],
@@ -12621,6 +13002,243 @@ var sessionCommands = [
12621
13002
  }
12622
13003
  ];
12623
13004
 
13005
+ // src/client/commands/ticket.ts
13006
+ var STATUSES = ["backlog", "in-progress", "review", "done"];
13007
+ var PROJECT_OPTION = {
13008
+ name: "project",
13009
+ kind: "string",
13010
+ valueName: "PROJECT_ID",
13011
+ description: "Project ID (project list)."
13012
+ };
13013
+ var STATUS_OPTION = {
13014
+ name: "status",
13015
+ kind: "string",
13016
+ valueName: "COLUMN",
13017
+ description: `Column: ${STATUSES.join(", ")}.`
13018
+ };
13019
+ var TEXT_FIELD_OPTIONS = [
13020
+ { name: "title", kind: "string", valueName: "TEXT", description: "Ticket title." },
13021
+ {
13022
+ name: "description",
13023
+ kind: "string",
13024
+ valueName: "TEXT",
13025
+ description: "Description (or --description-file)."
13026
+ },
13027
+ {
13028
+ name: "description-file",
13029
+ kind: "string",
13030
+ valueName: "PATH",
13031
+ description: "Read the description from a local file."
13032
+ },
13033
+ {
13034
+ name: "criteria",
13035
+ kind: "string",
13036
+ valueName: "TEXT",
13037
+ description: "Acceptance criteria."
13038
+ }
13039
+ ];
13040
+ function requireProject(options2) {
13041
+ const projectId = stringOption(options2, "project");
13042
+ if (!projectId)
13043
+ throw new CliError("invalid-input", "--project is required");
13044
+ return projectId;
13045
+ }
13046
+ async function ticketFields(io, options2) {
13047
+ const description = await readTextSource(io, {
13048
+ file: options2["description-file"],
13049
+ inline: options2.description,
13050
+ label: "Description",
13051
+ maxBytes: 400000,
13052
+ names: { file: "--description-file", stdin: "--description-stdin", inline: "--description" },
13053
+ required: false
13054
+ });
13055
+ return {
13056
+ ...stringOption(options2, "title") !== undefined ? { title: options2.title } : {},
13057
+ ...description !== undefined ? { description } : {},
13058
+ ...stringOption(options2, "criteria") !== undefined ? { acceptanceCriteria: options2.criteria } : {},
13059
+ ...options2.status !== undefined ? { status: oneOf(options2.status, STATUSES, "--status") } : {}
13060
+ };
13061
+ }
13062
+ function ticketRows(tickets) {
13063
+ return table(["ID", "STATUS", "TITLE", "COMMENTS"], tickets.map((ticket) => [ticket.id, ticket.status, ticket.title, String(ticket.commentCount)]));
13064
+ }
13065
+ function ticketLines(ticket) {
13066
+ return [
13067
+ ...keyValues([
13068
+ ["id", ticket.id],
13069
+ ["project", ticket.projectId],
13070
+ ["status", ticket.status],
13071
+ ["title", ticket.title],
13072
+ ["environment", ticket.environmentId],
13073
+ ["pr", ticket.pr.url]
13074
+ ]),
13075
+ ...ticket.description ? ["", ticket.description] : [],
13076
+ ...ticket.acceptanceCriteria ? ["", "Acceptance criteria:", ticket.acceptanceCriteria] : [],
13077
+ ...ticket.comments.flatMap((comment) => ["", `[${comment.createdAt}] ${comment.text}`])
13078
+ ];
13079
+ }
13080
+ var ticketCommands = [
13081
+ {
13082
+ path: ["ticket", "list"],
13083
+ action: "ticket.list",
13084
+ summary: "List a project's Kanban tickets.",
13085
+ positionals: [],
13086
+ options: [
13087
+ PROJECT_OPTION,
13088
+ STATUS_OPTION,
13089
+ { name: "limit", kind: "integer", valueName: "N", description: "Items per page (max 200)." },
13090
+ {
13091
+ name: "cursor",
13092
+ kind: "string",
13093
+ valueName: "CURSOR",
13094
+ description: "Continue from a previous page."
13095
+ }
13096
+ ],
13097
+ idOutput: "ticket IDs, one per line",
13098
+ async run(context, parsed) {
13099
+ const session = await context.session();
13100
+ const page = await session.read("ticket.list", {
13101
+ projectId: requireProject(parsed.options),
13102
+ ...parsed.options.status !== undefined ? { status: oneOf(parsed.options.status, STATUSES, "--status") } : {},
13103
+ ...parsed.options.limit !== undefined ? { limit: parsed.options.limit } : {},
13104
+ ...parsed.options.cursor !== undefined ? { cursor: parsed.options.cursor } : {}
13105
+ });
13106
+ return {
13107
+ action: "ticket.list",
13108
+ result: page,
13109
+ connection: session.identity,
13110
+ ids: page.items.map((ticket) => ticket.id),
13111
+ human: [
13112
+ ...ticketRows(page.items),
13113
+ ...page.nextCursor ? [`more: --cursor ${page.nextCursor}`] : []
13114
+ ]
13115
+ };
13116
+ }
13117
+ },
13118
+ {
13119
+ path: ["ticket", "get"],
13120
+ action: "ticket.get",
13121
+ summary: "Show one ticket with its description, criteria and latest comments.",
13122
+ positionals: [{ name: "ticket", required: true }],
13123
+ options: [PROJECT_OPTION],
13124
+ inputFields: { ticket: "ticketId" },
13125
+ idOutput: "ticket ID",
13126
+ async run(context, parsed) {
13127
+ const session = await context.session();
13128
+ const ticket = await session.read("ticket.get", {
13129
+ projectId: requireProject(parsed.options),
13130
+ ticketId: parsed.positionals.ticket
13131
+ });
13132
+ return {
13133
+ action: "ticket.get",
13134
+ result: ticket,
13135
+ connection: session.identity,
13136
+ ids: [ticket.id],
13137
+ human: ticketLines(ticket)
13138
+ };
13139
+ }
13140
+ },
13141
+ {
13142
+ path: ["ticket", "create"],
13143
+ action: "ticket.create",
13144
+ summary: "Create a Kanban ticket.",
13145
+ positionals: [],
13146
+ options: [PROJECT_OPTION, ...TEXT_FIELD_OPTIONS, STATUS_OPTION, REQUEST_ID_OPTION],
13147
+ inputFields: {
13148
+ "description-file": "description",
13149
+ criteria: "acceptanceCriteria"
13150
+ },
13151
+ idOutput: "ticket ID",
13152
+ async run(context, parsed) {
13153
+ const fields = await ticketFields(context.io, parsed.options);
13154
+ if (typeof fields.title !== "string")
13155
+ throw new CliError("invalid-input", "--title is required");
13156
+ const { session, result, receipt, warnings } = await submit(context, "ticket.create", { projectId: requireProject(parsed.options), ...fields }, parsed.options);
13157
+ return {
13158
+ action: "ticket.create",
13159
+ result,
13160
+ receipt,
13161
+ warnings,
13162
+ connection: session.identity,
13163
+ ids: [result.ticketId],
13164
+ human: [`created ticket ${result.ticketId}`]
13165
+ };
13166
+ }
13167
+ },
13168
+ {
13169
+ path: ["ticket", "update"],
13170
+ action: "ticket.update",
13171
+ summary: "Change a ticket's title, description, criteria or column.",
13172
+ positionals: [{ name: "ticket", required: true }],
13173
+ options: [PROJECT_OPTION, ...TEXT_FIELD_OPTIONS, STATUS_OPTION, REQUEST_ID_OPTION],
13174
+ inputFields: {
13175
+ ticket: "ticketId",
13176
+ "description-file": "description",
13177
+ criteria: "acceptanceCriteria"
13178
+ },
13179
+ idOutput: "ticket ID",
13180
+ async run(context, parsed) {
13181
+ const fields = await ticketFields(context.io, parsed.options);
13182
+ if (Object.keys(fields).length === 0) {
13183
+ throw new CliError("invalid-input", "Pass at least one field to change");
13184
+ }
13185
+ const { session, result, receipt, warnings } = await submit(context, "ticket.update", {
13186
+ projectId: requireProject(parsed.options),
13187
+ ticketId: parsed.positionals.ticket,
13188
+ ...fields
13189
+ }, parsed.options);
13190
+ return {
13191
+ action: "ticket.update",
13192
+ result,
13193
+ receipt,
13194
+ warnings,
13195
+ connection: session.identity,
13196
+ ids: [result.ticketId],
13197
+ human: [`updated ticket ${result.ticketId}`]
13198
+ };
13199
+ }
13200
+ },
13201
+ {
13202
+ path: ["ticket", "comment"],
13203
+ action: "ticket.comment",
13204
+ summary: "Append a comment to a ticket.",
13205
+ positionals: [{ name: "ticket", required: true }],
13206
+ options: [
13207
+ PROJECT_OPTION,
13208
+ { name: "text", kind: "string", valueName: "TEXT", description: "Comment text." },
13209
+ {
13210
+ name: "text-file",
13211
+ kind: "string",
13212
+ valueName: "PATH",
13213
+ description: "Read the comment from a local file."
13214
+ },
13215
+ REQUEST_ID_OPTION
13216
+ ],
13217
+ inputFields: { ticket: "ticketId", "text-file": "text" },
13218
+ idOutput: "ticket ID",
13219
+ async run(context, parsed) {
13220
+ const text = await readTextSource(context.io, {
13221
+ file: parsed.options["text-file"],
13222
+ inline: parsed.options.text,
13223
+ label: "Comment",
13224
+ maxBytes: 80000,
13225
+ names: { file: "--text-file", stdin: "--text-stdin", inline: "--text" },
13226
+ required: true
13227
+ });
13228
+ const { session, result, receipt, warnings } = await submit(context, "ticket.comment", { projectId: requireProject(parsed.options), ticketId: parsed.positionals.ticket, text }, parsed.options);
13229
+ return {
13230
+ action: "ticket.comment",
13231
+ result,
13232
+ receipt,
13233
+ warnings,
13234
+ connection: session.identity,
13235
+ ids: [result.ticketId],
13236
+ human: [`commented on ticket ${result.ticketId}`]
13237
+ };
13238
+ }
13239
+ }
13240
+ ];
13241
+
12624
13242
  // src/client/commands/toolchain.ts
12625
13243
  import path18 from "path";
12626
13244
  function dataDirectory2(context, parsed) {
@@ -13046,6 +13664,9 @@ var CLIENT_COMMANDS = [
13046
13664
  ...environmentCommands,
13047
13665
  ...sessionCommands,
13048
13666
  ...changesCommands,
13667
+ ...attentionCommands,
13668
+ ...ticketCommands,
13669
+ ...mailCommands,
13049
13670
  ...runCommands,
13050
13671
  ...toolchainCommands,
13051
13672
  ...hostCommands,