@opengeni/react 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 (85) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-5TIXRCSI.js → chunk-NFYVQWIB.js} +150 -64
  3. package/dist/chunk-NFYVQWIB.js.map +1 -0
  4. package/dist/index.d.ts +760 -142
  5. package/dist/index.js +5345 -2146
  6. package/dist/index.js.map +1 -1
  7. package/dist/{machines-BeZ3bD3t.d.ts → machines-CnlMb7E-.d.ts} +5 -5
  8. package/dist/machines.d.ts +1 -1
  9. package/dist/machines.js +1 -1
  10. package/package.json +17 -12
  11. package/src/approvals.ts +16 -4
  12. package/src/client.ts +32 -5
  13. package/src/commands/index.ts +1 -7
  14. package/src/commands/registry.ts +46 -12
  15. package/src/components/chat-composer.tsx +135 -39
  16. package/src/components/code-editor.tsx +28 -36
  17. package/src/components/command-palette.tsx +26 -5
  18. package/src/components/desktop-viewer.tsx +29 -16
  19. package/src/components/diff-view.tsx +27 -189
  20. package/src/components/enrollment-consent.tsx +28 -10
  21. package/src/components/enrollment-device-flow.tsx +8 -5
  22. package/src/components/file-browser.tsx +190 -56
  23. package/src/components/fleet-tile.tsx +13 -4
  24. package/src/components/machine-card.tsx +16 -4
  25. package/src/components/machine-dock-bar.tsx +20 -8
  26. package/src/components/machine-metrics.tsx +31 -8
  27. package/src/components/machine-status-pill.tsx +26 -5
  28. package/src/components/machines-dashboard.tsx +8 -2
  29. package/src/components/markdown.tsx +39 -9
  30. package/src/components/message-timeline.tsx +633 -109
  31. package/src/components/pierre-diff.tsx +83 -15
  32. package/src/components/pierre-file.tsx +10 -9
  33. package/src/components/sandbox-files.tsx +110 -227
  34. package/src/components/sandbox-terminal.tsx +256 -88
  35. package/src/components/sandbox-workspace.tsx +823 -0
  36. package/src/components/session-status.tsx +28 -2
  37. package/src/components/workbench-changes.tsx +541 -0
  38. package/src/components/workspace-dock.tsx +85 -22
  39. package/src/hooks/internal.ts +5 -2
  40. package/src/hooks/use-available-models.ts +7 -2
  41. package/src/hooks/use-billing-usage.ts +4 -1
  42. package/src/hooks/use-codex-accounts.ts +74 -44
  43. package/src/hooks/use-composer.ts +57 -36
  44. package/src/hooks/use-environments.ts +92 -47
  45. package/src/hooks/use-file-attachments.ts +101 -55
  46. package/src/hooks/use-goal.ts +52 -16
  47. package/src/hooks/use-machine-chip.ts +134 -0
  48. package/src/hooks/use-machines.ts +34 -16
  49. package/src/hooks/use-packs.ts +24 -19
  50. package/src/hooks/use-relay-frame-stream.ts +5 -2
  51. package/src/hooks/use-rigs.ts +335 -0
  52. package/src/hooks/use-sandbox-files.ts +213 -39
  53. package/src/hooks/use-sandbox-git.ts +188 -11
  54. package/src/hooks/use-sandbox-terminal.ts +18 -14
  55. package/src/hooks/use-scheduled-tasks.ts +10 -2
  56. package/src/hooks/use-session-capabilities.ts +77 -20
  57. package/src/hooks/use-session-control.ts +49 -21
  58. package/src/hooks/use-session-events.ts +48 -20
  59. package/src/hooks/use-session-lineage.ts +119 -0
  60. package/src/hooks/use-session.ts +43 -28
  61. package/src/hooks/use-slash-commands.ts +6 -4
  62. package/src/hooks/use-turn-queue.ts +74 -147
  63. package/src/hooks/use-windowed-sections.ts +204 -0
  64. package/src/hooks/use-workspace-capture.ts +233 -0
  65. package/src/hooks/use-workspace-edit.ts +231 -0
  66. package/src/hooks/use-workspace-sessions.ts +48 -5
  67. package/src/hooks/use-workspaces.ts +18 -15
  68. package/src/index.ts +127 -23
  69. package/src/lib/format.ts +3 -3
  70. package/src/lib/xterm-renderer.ts +65 -0
  71. package/src/lib/xterm-theme.ts +224 -18
  72. package/src/machines.ts +13 -3
  73. package/src/provider.tsx +3 -1
  74. package/src/timeline/activity-rail.tsx +213 -54
  75. package/src/timeline/disclosure-context.tsx +12 -2
  76. package/src/timeline/index.ts +21 -2
  77. package/src/timeline/parsers.ts +44 -10
  78. package/src/timeline/projection.ts +388 -85
  79. package/src/timeline/shared.tsx +111 -24
  80. package/src/timeline/tool-diff.tsx +24 -20
  81. package/src/timeline/tool-renderers.tsx +98 -21
  82. package/src/timeline/turn-summary.tsx +76 -23
  83. package/src/timeline/types.ts +96 -12
  84. package/styles/tokens.css +2 -2
  85. package/dist/chunk-5TIXRCSI.js.map +0 -1
@@ -7,7 +7,7 @@ import { ReactNode } from 'react';
7
7
  * pass the real SDK client, a proxy-backed client that routes through their
8
8
  * own API, or a scripted client in tests/demos.
9
9
  */
10
- type SessionClientLike = Pick<OpenGeniClient, "getClientConfig" | "getSession" | "updateSession" | "listSessions" | "sendMessage" | "steerMessage" | "interrupt" | "sendApprovalDecision" | "listEvents" | "streamEvents" | "listTurns" | "updateQueuedTurn" | "reorderQueuedTurns" | "deleteQueuedTurn" | "getGoal" | "updateGoal" | "clearSessionContext" | "compactSessionContext" | "listScheduledTasks" | "uploadFile" | "getFile" | "createFileDownloadUrl" | "listEnvironments" | "createEnvironment" | "updateEnvironment" | "deleteEnvironment" | "setEnvironmentVariable" | "deleteEnvironmentVariable" | "listPacks" | "registerPack" | "enablePack" | "deletePack" | "listWorkspaces" | "createWorkspace" | "updateWorkspace" | "getBillingUsage" | "getClientConfig" | "getStreamCapabilities" | "acknowledgeStream" | "attachViewer" | "heartbeatViewer" | "detachViewer" | "fsList" | "fsRead" | "fsWrite" | "fsDelete" | "fsMove" | "fsMkdir" | "gitStatus" | "gitDiff" | "terminalExec" | "terminalPtyOpen" | "terminalPtyWrite" | "terminalPtyResize" | "terminalPtyClose">;
10
+ type SessionClientLike = Pick<OpenGeniClient, "getClientConfig" | "getSession" | "getSessionLineage" | "updateSession" | "updateSessionPin" | "listSessions" | "listSessionPage" | "sendMessage" | "steerMessage" | "pauseSession" | "resumeSession" | "sendApprovalDecision" | "listEvents" | "streamEvents" | "getQueue" | "cancelQueueItem" | "listTurns" | "getGoal" | "updateGoal" | "deleteGoal" | "clearSessionContext" | "compactSessionContext" | "listScheduledTasks" | "uploadFile" | "getFile" | "createFileDownloadUrl" | "listVariableSets" | "createVariableSet" | "updateVariableSet" | "deleteVariableSet" | "setVariableSetVariable" | "deleteVariableSetVariable" | "listEnvironments" | "createEnvironment" | "updateEnvironment" | "deleteEnvironment" | "setEnvironmentVariable" | "deleteEnvironmentVariable" | "listRigs" | "createRig" | "getRig" | "updateRig" | "deleteRig" | "listRigVersions" | "activateRigVersion" | "listRigChanges" | "proposeRigChange" | "getRigChange" | "verifyRigChange" | "promoteRigChange" | "verifyRig" | "listPacks" | "registerPack" | "enablePack" | "deletePack" | "listWorkspaces" | "createWorkspace" | "updateWorkspace" | "getBillingUsage" | "getClientConfig" | "getStreamCapabilities" | "acknowledgeStream" | "attachViewer" | "heartbeatViewer" | "detachViewer" | "fsList" | "fsRead" | "fsWrite" | "fsDelete" | "fsMove" | "fsMkdir" | "gitStatus" | "gitDiff" | "getWorkspaceCapture" | "getWorkspaceCaptureFile" | "terminalExec" | "terminalPtyOpen" | "terminalPtyWrite" | "terminalPtyResize" | "terminalPtyClose">;
11
11
 
12
12
  type OpenGeniContextValue = {
13
13
  client: SessionClientLike;
@@ -76,7 +76,7 @@ type ConnectionStatusPillProps = {
76
76
  * the Machines dashboard, the dock header, and the session timeline. Reconnecting
77
77
  * breathes (the resiliency-as-headline blip), online/offline hold still.
78
78
  */
79
- declare function ConnectionStatusPill({ status, label, size, className }: ConnectionStatusPillProps): react_jsx_runtime.JSX.Element;
79
+ declare function ConnectionStatusPill({ status, label, size, className, }: ConnectionStatusPillProps): react_jsx_runtime.JSX.Element;
80
80
  type ConnectionDotProps = {
81
81
  status: ConnectionStatus;
82
82
  className?: string | undefined;
@@ -97,7 +97,7 @@ type MachineStatusPillProps = {
97
97
  * dashboard row, the dock header, and the timeline all reuse so the status
98
98
  * language is identical everywhere.
99
99
  */
100
- declare function MachineStatusPill({ state, sharedSessionCount, size, className }: MachineStatusPillProps): react_jsx_runtime.JSX.Element;
100
+ declare function MachineStatusPill({ state, sharedSessionCount, size, className, }: MachineStatusPillProps): react_jsx_runtime.JSX.Element;
101
101
 
102
102
  type MachineMetricsProps = {
103
103
  /** The latest metric sample, or null when the machine hasn't reported yet. */
@@ -171,7 +171,7 @@ type MachineDockBarProps = {
171
171
  * render IDENTICALLY regardless of backend — this bar is the only backend-aware
172
172
  * chrome (the dock-parity contract: selfhosted == Modal for the surfaces).
173
173
  */
174
- declare function MachineDockBar({ name, kind, state, sharedSessionCount, className }: MachineDockBarProps): react_jsx_runtime.JSX.Element;
174
+ declare function MachineDockBar({ name, kind, state, className }: MachineDockBarProps): react_jsx_runtime.JSX.Element;
175
175
  type SharedMachineDisclosureProps = {
176
176
  sharedSessionCount: number;
177
177
  /** Compact (inline strip) vs full (a notice block). */
@@ -183,7 +183,7 @@ type SharedMachineDisclosureProps = {
183
183
  * dock (and reused on the desktop take-control gate) so the user knows others are
184
184
  * driving the same whole-machine lease. Render when `sharedSessionCount > 1`.
185
185
  */
186
- declare function SharedMachineDisclosure({ sharedSessionCount, density, className }: SharedMachineDisclosureProps): react_jsx_runtime.JSX.Element;
186
+ declare function SharedMachineDisclosure({ sharedSessionCount, density, className, }: SharedMachineDisclosureProps): react_jsx_runtime.JSX.Element;
187
187
 
188
188
  /** The poll state of an in-flight device-flow enrollment. */
189
189
  type DeviceFlowPhase = "pending" | "authorized" | "denied" | "expired" | "disabled";
@@ -1,4 +1,4 @@
1
- export { a as CONNECTION_STATUS_META, b as ConnectionDot, c as ConnectionDotProps, d as ConnectionStatus, e as ConnectionStatusMeta, f as ConnectionStatusPill, g as ConnectionStatusPillProps, D as DeviceFlowPhase, E as EnrollmentConsent, h as EnrollmentConsentMachine, i as EnrollmentConsentPhase, j as EnrollmentConsentProps, k as EnrollmentDeviceFlow, l as EnrollmentDeviceFlowProps, M as MACHINE_STATE_BADGE_META, m as MachineCard, n as MachineCardProps, o as MachineDockBar, p as MachineDockBarProps, q as MachineMetrics, r as MachineMetricsProps, s as MachineStateBadgeMeta, t as MachineStatusPill, u as MachineStatusPillProps, v as MachinesClientLike, w as MachinesDashboard, x as MachinesDashboardProps, A as SharedMachineDisclosure, B as SharedMachineDisclosureProps, U as UseMachinesOptions, F as UseMachinesResult, G as connectionStatusForState, H as useMachines } from './machines-BeZ3bD3t.js';
1
+ export { a as CONNECTION_STATUS_META, b as ConnectionDot, c as ConnectionDotProps, d as ConnectionStatus, e as ConnectionStatusMeta, f as ConnectionStatusPill, g as ConnectionStatusPillProps, D as DeviceFlowPhase, E as EnrollmentConsent, h as EnrollmentConsentMachine, i as EnrollmentConsentPhase, j as EnrollmentConsentProps, k as EnrollmentDeviceFlow, l as EnrollmentDeviceFlowProps, M as MACHINE_STATE_BADGE_META, m as MachineCard, n as MachineCardProps, o as MachineDockBar, p as MachineDockBarProps, q as MachineMetrics, r as MachineMetricsProps, s as MachineStateBadgeMeta, t as MachineStatusPill, u as MachineStatusPillProps, v as MachinesClientLike, w as MachinesDashboard, x as MachinesDashboardProps, A as SharedMachineDisclosure, B as SharedMachineDisclosureProps, U as UseMachinesOptions, F as UseMachinesResult, G as connectionStatusForState, H as useMachines } from './machines-CnlMb7E-.js';
2
2
  export { MachineKind, MachineMetricsSeriesResponse, MachineState, MachineView, MachinesResponse, MetricSample } from '@opengeni/sdk';
3
3
  import 'react/jsx-runtime';
4
4
  import 'react';
package/dist/machines.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  SharedMachineDisclosure,
14
14
  connectionStatusForState,
15
15
  useMachines
16
- } from "./chunk-5TIXRCSI.js";
16
+ } from "./chunk-NFYVQWIB.js";
17
17
  export {
18
18
  CONNECTION_STATUS_META,
19
19
  ConnectionDot,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -8,6 +8,11 @@
8
8
  "url": "git+https://github.com/Cloudgeni-ai/opengeni.git",
9
9
  "directory": "packages/react"
10
10
  },
11
+ "files": [
12
+ "dist",
13
+ "src",
14
+ "styles"
15
+ ],
11
16
  "type": "module",
12
17
  "sideEffects": false,
13
18
  "main": "./dist/index.js",
@@ -25,20 +30,12 @@
25
30
  "./styles.css": "./styles/index.css",
26
31
  "./tokens.css": "./styles/tokens.css"
27
32
  },
28
- "files": [
29
- "dist",
30
- "src",
31
- "styles"
32
- ],
33
- "engines": {
34
- "node": ">=18"
35
- },
36
33
  "publishConfig": {
37
34
  "access": "public",
38
35
  "provenance": true
39
36
  },
40
37
  "scripts": {
41
- "typecheck": "tsc --noEmit",
38
+ "typecheck": "tsgo --noEmit",
42
39
  "build": "tsup",
43
40
  "demo": "vite dev demo --port 3100",
44
41
  "demo:build": "vite build demo",
@@ -46,7 +43,7 @@
46
43
  },
47
44
  "dependencies": {
48
45
  "@bufbuild/protobuf": "^2.2.0",
49
- "@opengeni/sdk": "^0.11.0",
46
+ "@opengeni/sdk": "^0.13.0",
50
47
  "clsx": "^2.1.1",
51
48
  "lucide-react": "^1.8.0",
52
49
  "motion": "^12.0.0",
@@ -54,7 +51,8 @@
54
51
  "react-markdown": "^10.1.0",
55
52
  "react-resizable-panels": "^4.11.2",
56
53
  "remark-gfm": "^4.0.1",
57
- "tailwind-merge": "^3.5.0"
54
+ "tailwind-merge": "^3.5.0",
55
+ "virtua": "^0.49.2"
58
56
  },
59
57
  "peerDependencies": {
60
58
  "@codemirror/lang-css": "^6.0.0",
@@ -68,6 +66,7 @@
68
66
  "@uiw/react-codemirror": "^4.23.0",
69
67
  "@xterm/addon-fit": "^0.11.0",
70
68
  "@xterm/addon-web-links": "^0.12.0",
69
+ "@xterm/addon-webgl": "^0.19.0",
71
70
  "@xterm/xterm": "^6.0.0",
72
71
  "react": "^18.0.0 || ^19.0.0",
73
72
  "react-dom": "^18.0.0 || ^19.0.0"
@@ -106,8 +105,14 @@
106
105
  "@xterm/addon-web-links": {
107
106
  "optional": true
108
107
  },
108
+ "@xterm/addon-webgl": {
109
+ "optional": true
110
+ },
109
111
  "@xterm/xterm": {
110
112
  "optional": true
111
113
  }
114
+ },
115
+ "engines": {
116
+ "node": ">=18"
112
117
  }
113
118
  }
package/src/approvals.ts CHANGED
@@ -33,13 +33,22 @@ export type PendingApproval = {
33
33
 
34
34
  /** The approvals carried by one `session.requiresAction` payload. */
35
35
  export function approvalsFromRequiresAction(payload: unknown): PendingApproval[] {
36
- const approvals = payload && typeof payload === "object" ? (payload as { approvals?: unknown }).approvals : undefined;
36
+ const approvals =
37
+ payload && typeof payload === "object"
38
+ ? (payload as { approvals?: unknown }).approvals
39
+ : undefined;
37
40
  if (!Array.isArray(approvals)) {
38
41
  return [];
39
42
  }
40
43
  return approvals.map((approval, index) => {
41
- const raw = (approval && typeof approval === "object" ? approval : {}) as Record<string, unknown>;
42
- const rawItem = raw.rawItem && typeof raw.rawItem === "object" ? raw.rawItem as Record<string, unknown> : {};
44
+ const raw = (approval && typeof approval === "object" ? approval : {}) as Record<
45
+ string,
46
+ unknown
47
+ >;
48
+ const rawItem =
49
+ raw.rawItem && typeof raw.rawItem === "object"
50
+ ? (raw.rawItem as Record<string, unknown>)
51
+ : {};
43
52
  return {
44
53
  id: String(raw.id ?? raw.callId ?? rawItem.callId ?? index),
45
54
  name: String(raw.name ?? "approval"),
@@ -61,7 +70,10 @@ export function projectPendingApprovals(events: SessionEvent[]): PendingApproval
61
70
  break;
62
71
  }
63
72
  case "user.approvalDecision": {
64
- const payload = event.payload && typeof event.payload === "object" ? event.payload as { approvalId?: unknown } : {};
73
+ const payload =
74
+ event.payload && typeof event.payload === "object"
75
+ ? (event.payload as { approvalId?: unknown })
76
+ : {};
65
77
  if (typeof payload.approvalId === "string") {
66
78
  pending = pending.filter((approval) => approval.id !== payload.approvalId);
67
79
  }
package/src/client.ts CHANGED
@@ -11,22 +11,26 @@ export type SessionClientLike = Pick<
11
11
  | "getClientConfig"
12
12
  // Sessions, events, composer
13
13
  | "getSession"
14
+ | "getSessionLineage"
14
15
  | "updateSession"
16
+ | "updateSessionPin"
15
17
  | "listSessions"
18
+ | "listSessionPage"
16
19
  | "sendMessage"
17
20
  | "steerMessage"
18
- | "interrupt"
21
+ | "pauseSession"
22
+ | "resumeSession"
19
23
  | "sendApprovalDecision"
20
24
  | "listEvents"
21
25
  | "streamEvents"
22
26
  // Turn queue
27
+ | "getQueue"
28
+ | "cancelQueueItem"
23
29
  | "listTurns"
24
- | "updateQueuedTurn"
25
- | "reorderQueuedTurns"
26
- | "deleteQueuedTurn"
27
30
  // Goal
28
31
  | "getGoal"
29
32
  | "updateGoal"
33
+ | "deleteGoal"
30
34
  // Operator context controls (/clear, /compact)
31
35
  | "clearSessionContext"
32
36
  | "compactSessionContext"
@@ -36,13 +40,33 @@ export type SessionClientLike = Pick<
36
40
  | "uploadFile"
37
41
  | "getFile"
38
42
  | "createFileDownloadUrl"
39
- // Environments
43
+ // VariableSets
44
+ | "listVariableSets"
45
+ | "createVariableSet"
46
+ | "updateVariableSet"
47
+ | "deleteVariableSet"
48
+ | "setVariableSetVariable"
49
+ | "deleteVariableSetVariable"
40
50
  | "listEnvironments"
41
51
  | "createEnvironment"
42
52
  | "updateEnvironment"
43
53
  | "deleteEnvironment"
44
54
  | "setEnvironmentVariable"
45
55
  | "deleteEnvironmentVariable"
56
+ // Rigs
57
+ | "listRigs"
58
+ | "createRig"
59
+ | "getRig"
60
+ | "updateRig"
61
+ | "deleteRig"
62
+ | "listRigVersions"
63
+ | "activateRigVersion"
64
+ | "listRigChanges"
65
+ | "proposeRigChange"
66
+ | "getRigChange"
67
+ | "verifyRigChange"
68
+ | "promoteRigChange"
69
+ | "verifyRig"
46
70
  // Packs
47
71
  | "listPacks"
48
72
  | "registerPack"
@@ -69,6 +93,9 @@ export type SessionClientLike = Pick<
69
93
  | "fsMkdir"
70
94
  | "gitStatus"
71
95
  | "gitDiff"
96
+ // Workbench v2 turn-end capture reads (the cold-paint source; M3 consumes these)
97
+ | "getWorkspaceCapture"
98
+ | "getWorkspaceCaptureFile"
72
99
  | "terminalExec"
73
100
  | "terminalPtyOpen"
74
101
  | "terminalPtyWrite"
@@ -1,10 +1,4 @@
1
- export type {
2
- CommandContext,
3
- CommandResult,
4
- Notice,
5
- SlashArg,
6
- SlashCommand,
7
- } from "./types";
1
+ export type { CommandContext, CommandResult, Notice, SlashArg, SlashCommand } from "./types";
8
2
  export {
9
3
  argHint,
10
4
  defaultCommands,
@@ -40,7 +40,10 @@ export function parseCommandLine(value: string): ParsedCommandLine | null {
40
40
  const PERMISSION_SUPERUSER: Permission = "workspace:admin";
41
41
 
42
42
  /** Whether the operator's permission set satisfies a command's gate. */
43
- export function hasPermission(required: Permission | undefined, permissions: Permission[]): boolean {
43
+ export function hasPermission(
44
+ required: Permission | undefined,
45
+ permissions: Permission[],
46
+ ): boolean {
44
47
  if (!required) {
45
48
  return true;
46
49
  }
@@ -48,13 +51,18 @@ export function hasPermission(required: Permission | undefined, permissions: Per
48
51
  }
49
52
 
50
53
  /** Match a command (by name or alias) against the value. */
51
- export function matchCommand(commands: readonly SlashCommand[], value: string): SlashCommand | null {
54
+ export function matchCommand(
55
+ commands: readonly SlashCommand[],
56
+ value: string,
57
+ ): SlashCommand | null {
52
58
  const parsed = parseCommandLine(value);
53
59
  if (!parsed) {
54
60
  return null;
55
61
  }
56
62
  const token = parsed.name.toLowerCase();
57
- return commands.find((command) => command.name === token || command.aliases?.includes(token)) ?? null;
63
+ return (
64
+ commands.find((command) => command.name === token || command.aliases?.includes(token)) ?? null
65
+ );
58
66
  }
59
67
 
60
68
  type FilterCtx = Pick<CommandContext, "sessionId" | "status" | "permissions">;
@@ -64,7 +72,11 @@ type FilterCtx = Pick<CommandContext, "sessionId" | "status" | "permissions">;
64
72
  * `available()===false` commands are dropped entirely (a gated command is never
65
73
  * shown, not shown-disabled). Filtering is a prefix match on name/alias.
66
74
  */
67
- export function filterCommands(commands: readonly SlashCommand[], token: string, ctx: FilterCtx): SlashCommand[] {
75
+ export function filterCommands(
76
+ commands: readonly SlashCommand[],
77
+ token: string,
78
+ ctx: FilterCtx,
79
+ ): SlashCommand[] {
68
80
  const needle = token.toLowerCase();
69
81
  return commands.filter((command) => {
70
82
  if (!hasPermission(command.permission, ctx.permissions)) {
@@ -76,7 +88,10 @@ export function filterCommands(commands: readonly SlashCommand[], token: string,
76
88
  if (needle.length === 0) {
77
89
  return true;
78
90
  }
79
- return command.name.startsWith(needle) || (command.aliases?.some((alias) => alias.startsWith(needle)) ?? false);
91
+ return (
92
+ command.name.startsWith(needle) ||
93
+ (command.aliases?.some((alias) => alias.startsWith(needle)) ?? false)
94
+ );
80
95
  });
81
96
  }
82
97
 
@@ -139,7 +154,10 @@ export const defaultCommands: readonly SlashCommand[] = [
139
154
  // instead so the operator isn't told something happened when nothing did.
140
155
  const cleared = ctx.clearView();
141
156
  if (!cleared) {
142
- return { status: "error", message: "This view can't be cleared here (no local timeline to reset)." };
157
+ return {
158
+ status: "error",
159
+ message: "This view can't be cleared here (no local timeline to reset).",
160
+ };
143
161
  }
144
162
  return { status: "ok", message: "Local view cleared." };
145
163
  },
@@ -149,7 +167,9 @@ export const defaultCommands: readonly SlashCommand[] = [
149
167
  description: "Pause or resume the session's goal loop.",
150
168
  permission: "sessions:control",
151
169
  available: hasSession,
152
- args: [{ name: "action", required: true, oneOf: ["pause", "resume"], description: "pause | resume" }],
170
+ args: [
171
+ { name: "action", required: true, oneOf: ["pause", "resume"], description: "pause | resume" },
172
+ ],
153
173
  run: async (args, ctx) => {
154
174
  const sessionId = requireSession(ctx);
155
175
  const action = args[0];
@@ -157,7 +177,9 @@ export const defaultCommands: readonly SlashCommand[] = [
157
177
  return { status: "error", message: "Usage: /goal pause | /goal resume" };
158
178
  }
159
179
  try {
160
- await ctx.client.updateGoal(ctx.workspaceId, sessionId, { status: action === "pause" ? "paused" : "active" });
180
+ await ctx.client.updateGoal(ctx.workspaceId, sessionId, {
181
+ status: action === "pause" ? "paused" : "active",
182
+ });
161
183
  return { status: "ok", message: action === "pause" ? "Goal paused." : "Goal resumed." };
162
184
  } catch (cause) {
163
185
  return { status: "error", message: goalErrorMessage(cause, action) };
@@ -204,14 +226,24 @@ export const defaultCommands: readonly SlashCommand[] = [
204
226
  ];
205
227
 
206
228
  function errorMessage(cause: unknown): string | undefined {
207
- if (cause && typeof cause === "object" && "message" in cause && typeof (cause as { message?: unknown }).message === "string") {
229
+ if (
230
+ cause &&
231
+ typeof cause === "object" &&
232
+ "message" in cause &&
233
+ typeof (cause as { message?: unknown }).message === "string"
234
+ ) {
208
235
  return (cause as { message: string }).message;
209
236
  }
210
237
  return undefined;
211
238
  }
212
239
 
213
240
  function statusCode(cause: unknown): number | undefined {
214
- if (cause && typeof cause === "object" && "status" in cause && typeof (cause as { status?: unknown }).status === "number") {
241
+ if (
242
+ cause &&
243
+ typeof cause === "object" &&
244
+ "status" in cause &&
245
+ typeof (cause as { status?: unknown }).status === "number"
246
+ ) {
215
247
  return (cause as { status: number }).status;
216
248
  }
217
249
  return undefined;
@@ -223,14 +255,16 @@ function goalErrorMessage(cause: unknown, action: "pause" | "resume"): string {
223
255
  return "This session has no goal to control.";
224
256
  }
225
257
  if (code === 409) {
226
- return action === "resume" ? "Only a paused goal can be resumed." : "Goal is already in a terminal state.";
258
+ return action === "resume"
259
+ ? "Only a paused goal can be resumed."
260
+ : "Goal is already in a terminal state.";
227
261
  }
228
262
  return errorMessage(cause) ?? `Could not ${action} the goal.`;
229
263
  }
230
264
 
231
265
  function clearErrorMessage(cause: unknown): string {
232
266
  if (statusCode(cause) === 409) {
233
- return "Can't clear context mid-turn stop the current turn first.";
267
+ return "Pause the session and wait for the current inference to settle, then clear context.";
234
268
  }
235
269
  return errorMessage(cause) ?? "Could not clear context.";
236
270
  }