@stigmer/react 0.0.53 → 0.0.55

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 (118) hide show
  1. package/execution/ArtifactCard.d.ts +11 -1
  2. package/execution/ArtifactCard.d.ts.map +1 -1
  3. package/execution/ArtifactCard.js +22 -3
  4. package/execution/ArtifactCard.js.map +1 -1
  5. package/execution/ArtifactPreviewModal.d.ts.map +1 -1
  6. package/execution/ArtifactPreviewModal.js +1 -1
  7. package/execution/ArtifactPreviewModal.js.map +1 -1
  8. package/execution/ArtifactsWidget.d.ts +26 -19
  9. package/execution/ArtifactsWidget.d.ts.map +1 -1
  10. package/execution/ArtifactsWidget.js +32 -26
  11. package/execution/ArtifactsWidget.js.map +1 -1
  12. package/execution/MessageThread.d.ts +10 -1
  13. package/execution/MessageThread.d.ts.map +1 -1
  14. package/execution/MessageThread.js +21 -17
  15. package/execution/MessageThread.js.map +1 -1
  16. package/execution/SandboxContext.d.ts +32 -0
  17. package/execution/SandboxContext.d.ts.map +1 -0
  18. package/execution/SandboxContext.js +26 -0
  19. package/execution/SandboxContext.js.map +1 -0
  20. package/execution/SetupProgress.d.ts +23 -13
  21. package/execution/SetupProgress.d.ts.map +1 -1
  22. package/execution/SetupProgress.js +18 -12
  23. package/execution/SetupProgress.js.map +1 -1
  24. package/execution/ToolArgsView.d.ts.map +1 -1
  25. package/execution/ToolArgsView.js +3 -1
  26. package/execution/ToolArgsView.js.map +1 -1
  27. package/execution/ToolCallDetail.d.ts.map +1 -1
  28. package/execution/ToolCallDetail.js +3 -1
  29. package/execution/ToolCallDetail.js.map +1 -1
  30. package/execution/ToolCallItem.d.ts.map +1 -1
  31. package/execution/ToolCallItem.js +7 -1
  32. package/execution/ToolCallItem.js.map +1 -1
  33. package/execution/WriteBackCard.d.ts +34 -0
  34. package/execution/WriteBackCard.d.ts.map +1 -0
  35. package/execution/WriteBackCard.js +75 -0
  36. package/execution/WriteBackCard.js.map +1 -0
  37. package/execution/WriteBacksWidget.d.ts +49 -0
  38. package/execution/WriteBacksWidget.d.ts.map +1 -0
  39. package/execution/WriteBacksWidget.js +44 -0
  40. package/execution/WriteBacksWidget.js.map +1 -0
  41. package/execution/__tests__/file-path-resolver.test.d.ts +2 -0
  42. package/execution/__tests__/file-path-resolver.test.d.ts.map +1 -0
  43. package/execution/__tests__/file-path-resolver.test.js +180 -0
  44. package/execution/__tests__/file-path-resolver.test.js.map +1 -0
  45. package/execution/file-path-resolver.d.ts +3 -3
  46. package/execution/file-path-resolver.d.ts.map +1 -1
  47. package/execution/file-path-resolver.js +23 -12
  48. package/execution/file-path-resolver.js.map +1 -1
  49. package/execution/index.d.ts +9 -0
  50. package/execution/index.d.ts.map +1 -1
  51. package/execution/index.js +5 -0
  52. package/execution/index.js.map +1 -1
  53. package/execution/sandbox-path-normalizer.d.ts +46 -0
  54. package/execution/sandbox-path-normalizer.d.ts.map +1 -0
  55. package/execution/sandbox-path-normalizer.js +73 -0
  56. package/execution/sandbox-path-normalizer.js.map +1 -0
  57. package/execution/useArtifactContent.d.ts +5 -1
  58. package/execution/useArtifactContent.d.ts.map +1 -1
  59. package/execution/useArtifactContent.js +6 -2
  60. package/execution/useArtifactContent.js.map +1 -1
  61. package/execution/useWorkspaceWriteBacks.d.ts +40 -0
  62. package/execution/useWorkspaceWriteBacks.d.ts.map +1 -0
  63. package/execution/useWorkspaceWriteBacks.js +41 -0
  64. package/execution/useWorkspaceWriteBacks.js.map +1 -0
  65. package/github/GitHubRepoPicker.d.ts +5 -2
  66. package/github/GitHubRepoPicker.d.ts.map +1 -1
  67. package/github/GitHubRepoPicker.js +133 -36
  68. package/github/GitHubRepoPicker.js.map +1 -1
  69. package/github/index.d.ts +1 -0
  70. package/github/index.d.ts.map +1 -1
  71. package/github/index.js +1 -0
  72. package/github/index.js.map +1 -1
  73. package/github/useGitHubSearch.d.ts +20 -0
  74. package/github/useGitHubSearch.d.ts.map +1 -0
  75. package/github/useGitHubSearch.js +127 -0
  76. package/github/useGitHubSearch.js.map +1 -0
  77. package/index.d.ts +6 -6
  78. package/index.d.ts.map +1 -1
  79. package/index.js +3 -3
  80. package/index.js.map +1 -1
  81. package/package.json +4 -4
  82. package/session/index.d.ts +4 -0
  83. package/session/index.d.ts.map +1 -1
  84. package/session/index.js +2 -0
  85. package/session/index.js.map +1 -1
  86. package/session/useSessionArtifacts.d.ts +73 -0
  87. package/session/useSessionArtifacts.d.ts.map +1 -0
  88. package/session/useSessionArtifacts.js +95 -0
  89. package/session/useSessionArtifacts.js.map +1 -0
  90. package/session/useSessionWriteBacks.d.ts +56 -0
  91. package/session/useSessionWriteBacks.d.ts.map +1 -0
  92. package/session/useSessionWriteBacks.js +56 -0
  93. package/session/useSessionWriteBacks.js.map +1 -0
  94. package/src/execution/ArtifactCard.tsx +40 -0
  95. package/src/execution/ArtifactPreviewModal.tsx +2 -0
  96. package/src/execution/ArtifactsWidget.tsx +67 -43
  97. package/src/execution/MessageThread.tsx +23 -1
  98. package/src/execution/SandboxContext.ts +47 -0
  99. package/src/execution/SetupProgress.tsx +33 -14
  100. package/src/execution/ToolArgsView.tsx +3 -1
  101. package/src/execution/ToolCallDetail.tsx +3 -1
  102. package/src/execution/ToolCallItem.tsx +7 -1
  103. package/src/execution/WriteBackCard.tsx +210 -0
  104. package/src/execution/WriteBacksWidget.tsx +82 -0
  105. package/src/execution/__tests__/file-path-resolver.test.ts +295 -0
  106. package/src/execution/file-path-resolver.ts +24 -12
  107. package/src/execution/index.ts +13 -0
  108. package/src/execution/sandbox-path-normalizer.ts +80 -0
  109. package/src/execution/useArtifactContent.ts +6 -1
  110. package/src/execution/useWorkspaceWriteBacks.ts +56 -0
  111. package/src/github/GitHubRepoPicker.tsx +413 -108
  112. package/src/github/index.ts +5 -0
  113. package/src/github/useGitHubSearch.ts +162 -0
  114. package/src/index.ts +14 -0
  115. package/src/session/index.ts +12 -0
  116. package/src/session/useSessionArtifacts.ts +143 -0
  117. package/src/session/useSessionWriteBacks.ts +94 -0
  118. package/styles.css +1 -1
@@ -1,22 +1,27 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
3
+ import { useCallback, useMemo, useState } from "react";
4
4
  import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
5
- import type { ExecutionArtifact } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/artifact_pb";
6
- import { ExecutionPhase } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
7
5
  import { cn } from "@stigmer/theme";
8
- import { useExecutionArtifacts } from "./useExecutionArtifacts";
9
- import { isTerminalPhase } from "./execution-phases";
6
+ import {
7
+ useSessionArtifacts,
8
+ type SessionArtifactEntry,
9
+ } from "../session/useSessionArtifacts";
10
10
  import { ArtifactCard } from "./ArtifactCard";
11
11
  import { ArtifactPreviewModal } from "./ArtifactPreviewModal";
12
12
  import type { ApplyResourceResult } from "../library/useApplyResource";
13
13
 
14
14
  export interface ArtifactsWidgetProps {
15
15
  /**
16
- * The execution to display artifacts for. Renders nothing when `null`
17
- * or when the execution has no artifacts.
16
+ * All executions for the current session both completed and
17
+ * actively streaming. The widget aggregates artifacts across every
18
+ * execution, deduplicates by `sandbox_path` (latest wins), and
19
+ * sorts alphabetically by name.
20
+ *
21
+ * Renders nothing when the list is empty or no execution has
22
+ * artifacts.
18
23
  */
19
- readonly execution: AgentExecution | null;
24
+ readonly executions: readonly AgentExecution[];
20
25
  /** Organization slug for the "Apply to [org]" CTA in the preview modal. */
21
26
  readonly org: string;
22
27
  /**
@@ -31,24 +36,22 @@ export interface ArtifactsWidgetProps {
31
36
  }
32
37
 
33
38
  /**
34
- * Right-sidebar widget that surfaces execution artifacts as a compact
35
- * card list with automatic Stigmer resource detection.
39
+ * Right-sidebar widget that surfaces all artifacts produced during a
40
+ * session as a unified, alphabetically-sorted file listing.
41
+ *
42
+ * Artifacts from multiple executions are aggregated and deduplicated
43
+ * by `sandbox_path` (latest execution wins), presenting the user with
44
+ * a file-explorer-like view of the conversation's output — no
45
+ * execution/turn concepts are exposed.
36
46
  *
37
47
  * Composes {@link ArtifactCard} (summary + detection badges) with
38
48
  * {@link ArtifactPreviewModal} (full content review + Apply/Push CTA).
39
49
  * The card's "Preview" action opens the modal; the modal is the sole
40
50
  * location for Apply/Push actions (review-before-apply pattern).
41
51
  *
42
- * Derives all data from the `execution` prop:
43
- *
44
- * - **Artifacts**: extracted via {@link useExecutionArtifacts}
45
- * - **Terminal phase**: derived via {@link isTerminalPhase} — controls
46
- * whether the Apply CTA in the modal is enabled
47
- * - **Execution ID**: read from `execution.metadata.id`
48
- *
49
- * Returns `null` when the execution is `null` or has no artifacts,
50
- * matching the conditional-render pattern of {@link ExecutionProgress}
51
- * and {@link ExecutionCostSummary}.
52
+ * Returns `null` when the executions list is empty or no execution
53
+ * has artifacts, matching the conditional-render pattern of
54
+ * {@link ExecutionProgress} and {@link ExecutionCostSummary}.
52
55
  *
53
56
  * Renders without card chrome — each {@link ArtifactCard} provides its
54
57
  * own border and padding. The consumer controls the container styling
@@ -58,10 +61,13 @@ export interface ArtifactsWidgetProps {
58
61
  *
59
62
  * @example
60
63
  * ```tsx
61
- * const stream = useExecutionStream(executionId);
64
+ * const conv = useSessionConversation(sessionId, org);
62
65
  *
63
66
  * <ArtifactsWidget
64
- * execution={stream.execution}
67
+ * executions={[
68
+ * ...conv.completedExecutions,
69
+ * ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
70
+ * ]}
65
71
  * org={activeOrg}
66
72
  * onApplied={(result) => toast(`${result.kind} applied`)}
67
73
  * />
@@ -69,29 +75,46 @@ export interface ArtifactsWidgetProps {
69
75
  *
70
76
  * @see {@link ArtifactCard} — compact summary card per artifact
71
77
  * @see {@link ArtifactPreviewModal} — full preview with Apply/Push CTA
72
- * @see {@link useExecutionArtifacts} — headless artifact extraction hook
78
+ * @see {@link useSessionArtifacts} — headless session-level artifact aggregation hook
79
+ * @see {@link useExecutionArtifacts} — headless single-execution artifact extraction hook
73
80
  */
74
81
  export function ArtifactsWidget({
75
- execution,
82
+ executions,
76
83
  org,
77
84
  onApplied,
78
85
  className,
79
86
  }: ArtifactsWidgetProps) {
80
87
  const { artifacts, hasArtifacts, artifactCount } =
81
- useExecutionArtifacts(execution);
88
+ useSessionArtifacts(executions);
82
89
 
83
- const [previewArtifact, setPreviewArtifact] =
84
- useState<ExecutionArtifact | null>(null);
90
+ // Store the dedup key (sandboxPath or name) instead of a snapshot of
91
+ // the full entry. The actual entry is derived from the live artifacts
92
+ // list on every render, so the preview modal always reflects the
93
+ // latest artifact version — even when a newer execution publishes an
94
+ // updated artifact for the same path while the modal is open.
95
+ const [previewKey, setPreviewKey] = useState<string | null>(null);
85
96
 
86
- if (!hasArtifacts) return null;
97
+ const previewEntry = useMemo<SessionArtifactEntry | null>(
98
+ () =>
99
+ previewKey !== null
100
+ ? artifacts.find(
101
+ (e) =>
102
+ (e.artifact.sandboxPath || e.artifact.name) === previewKey,
103
+ ) ?? null
104
+ : null,
105
+ [previewKey, artifacts],
106
+ );
107
+
108
+ const handlePreview = useCallback(
109
+ (entry: SessionArtifactEntry) =>
110
+ setPreviewKey(entry.artifact.sandboxPath || entry.artifact.name),
111
+ [],
112
+ );
87
113
 
88
- const phase =
89
- execution?.status?.phase ?? ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED;
90
- const isTerminal = isTerminalPhase(phase);
91
- const executionId = execution?.metadata?.id ?? "";
114
+ if (!hasArtifacts) return null;
92
115
 
93
116
  return (
94
- <section aria-label="Execution artifacts" className={cn(className)}>
117
+ <section aria-label="Artifacts" className={cn(className)}>
95
118
  <div className="mb-2 flex items-center gap-2">
96
119
  <h3 className="text-sm font-medium text-foreground">Artifacts</h3>
97
120
  <span className="inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-muted px-1.5 text-xs tabular-nums text-muted-foreground">
@@ -100,26 +123,27 @@ export function ArtifactsWidget({
100
123
  </div>
101
124
 
102
125
  <div role="list" className="space-y-2">
103
- {artifacts.map((artifact) => (
104
- <div key={artifact.storageKey} role="listitem">
126
+ {artifacts.map((entry) => (
127
+ <div key={entry.artifact.storageKey} role="listitem">
105
128
  <ArtifactCard
106
- artifact={artifact}
107
- executionId={executionId}
129
+ artifact={entry.artifact}
130
+ executionId={entry.executionId}
108
131
  org={org}
109
- onPreview={setPreviewArtifact}
132
+ hasNameCollision={entry.hasNameCollision}
133
+ onPreview={() => handlePreview(entry)}
110
134
  />
111
135
  </div>
112
136
  ))}
113
137
  </div>
114
138
 
115
- {previewArtifact && (
139
+ {previewEntry && (
116
140
  <ArtifactPreviewModal
117
- artifact={previewArtifact}
118
- executionId={executionId}
141
+ artifact={previewEntry.artifact}
142
+ executionId={previewEntry.executionId}
119
143
  org={org}
120
- isTerminal={isTerminal}
144
+ isTerminal={previewEntry.isTerminal}
121
145
  open
122
- onClose={() => setPreviewArtifact(null)}
146
+ onClose={() => setPreviewKey(null)}
123
147
  onApplied={onApplied}
124
148
  />
125
149
  )}
@@ -22,6 +22,7 @@ import { SetupProgress } from "./SetupProgress";
22
22
  import { ApprovalCard } from "./ApprovalCard";
23
23
  import { FilePathContext, type FilePathContextValue } from "./FilePathContext";
24
24
  import type { ResolvedPathAction } from "./file-path-resolver";
25
+ import { SandboxContext, type SandboxContextValue } from "./SandboxContext";
25
26
 
26
27
  export interface MessageThreadProps {
27
28
  /** Completed executions in chronological order. */
@@ -87,6 +88,15 @@ export interface MessageThreadProps {
87
88
  path: string,
88
89
  resolved: ResolvedPathAction,
89
90
  ) => void;
91
+ /**
92
+ * Absolute sandbox workspace root (e.g. `/home/daytona/workspace`).
93
+ * When provided, shell commands and tool output normalize absolute
94
+ * sandbox paths to workspace-relative display paths.
95
+ *
96
+ * Pass an empty string or omit for local sessions where paths are
97
+ * the user's own filesystem (no normalization needed).
98
+ */
99
+ readonly sandboxWorkspaceRoot?: string;
90
100
  }
91
101
 
92
102
  const AUTO_SCROLL_THRESHOLD_PX = 80;
@@ -103,7 +113,7 @@ type ThreadItem =
103
113
  | { readonly kind: "phase-badge"; readonly phase: ExecutionPhase; readonly key: string }
104
114
  | { readonly kind: "pending-message"; readonly content: string; readonly key: string }
105
115
  | { readonly kind: "approval-request"; readonly pendingApproval: PendingApproval; readonly key: string }
106
- | { readonly kind: "setup-progress"; readonly workspaceEntries: readonly WorkspaceEntry[]; readonly key: string };
116
+ | { readonly kind: "setup-progress"; readonly workspaceEntries: readonly WorkspaceEntry[]; readonly serverPhase?: string; readonly key: string };
107
117
 
108
118
  function hasAiMessages(execution: AgentExecution): boolean {
109
119
  const messages = execution.status?.messages;
@@ -186,9 +196,12 @@ function buildThreadItems(
186
196
  lastPhase === ExecutionPhase.EXECUTION_PHASE_UNSPECIFIED) &&
187
197
  !hasAiMessages(activeStreamExecution)
188
198
  ) {
199
+ const serverPhase =
200
+ activeStreamExecution.status?.setupProgress?.currentPhase || undefined;
189
201
  items.push({
190
202
  kind: "setup-progress",
191
203
  workspaceEntries: workspaceEntries ?? [],
204
+ serverPhase,
192
205
  key: "setup-progress",
193
206
  });
194
207
  }
@@ -266,6 +279,7 @@ export function MessageThread({
266
279
  dismissedApprovalIds,
267
280
  workspaceEntries,
268
281
  onFilePathClick,
282
+ sandboxWorkspaceRoot,
269
283
  }: MessageThreadProps) {
270
284
  const scrollRef = useRef<HTMLDivElement>(null);
271
285
  const isNearBottomRef = useRef(true);
@@ -298,6 +312,11 @@ export function MessageThread({
298
312
  [workspaceEntries, onFilePathClick],
299
313
  );
300
314
 
315
+ const sandboxCtx = useMemo<SandboxContextValue>(
316
+ () => ({ sandboxWorkspaceRoot: sandboxWorkspaceRoot ?? "" }),
317
+ [sandboxWorkspaceRoot],
318
+ );
319
+
301
320
  return (
302
321
  <div
303
322
  ref={scrollRef}
@@ -314,6 +333,7 @@ export function MessageThread({
314
333
  className,
315
334
  )}
316
335
  >
336
+ <SandboxContext.Provider value={sandboxCtx}>
317
337
  <FilePathContext.Provider value={filePathCtx}>
318
338
  {items.map((item) => {
319
339
  switch (item.kind) {
@@ -352,6 +372,7 @@ export function MessageThread({
352
372
  <SetupProgress
353
373
  key={item.key}
354
374
  workspaceEntries={item.workspaceEntries}
375
+ serverPhase={item.serverPhase}
355
376
  />
356
377
  );
357
378
  case "pending-message":
@@ -370,6 +391,7 @@ export function MessageThread({
370
391
  }
371
392
  })}
372
393
  </FilePathContext.Provider>
394
+ </SandboxContext.Provider>
373
395
  </div>
374
396
  );
375
397
  }
@@ -0,0 +1,47 @@
1
+ import { createContext, useCallback, useContext } from "react";
2
+ import { normalizeSandboxPaths } from "./sandbox-path-normalizer";
3
+
4
+ /**
5
+ * Context value that carries the sandbox workspace root for display-time
6
+ * path normalization.
7
+ *
8
+ * Provided by {@link MessageThread} (or a platform builder's custom
9
+ * wrapper). When no provider is present, path normalization is a no-op.
10
+ */
11
+ export interface SandboxContextValue {
12
+ /**
13
+ * Absolute sandbox workspace root (e.g. `/home/daytona/workspace`).
14
+ * Empty string disables normalization (local mode, backward compat).
15
+ */
16
+ readonly sandboxWorkspaceRoot: string;
17
+ }
18
+
19
+ const DEFAULT_VALUE: SandboxContextValue = {
20
+ sandboxWorkspaceRoot: "",
21
+ };
22
+
23
+ export const SandboxContext =
24
+ createContext<SandboxContextValue>(DEFAULT_VALUE);
25
+
26
+ /**
27
+ * Returns a stable normalizer function that replaces absolute sandbox
28
+ * paths in the given text with workspace-relative display paths.
29
+ *
30
+ * When `sandboxWorkspaceRoot` is empty (no provider or local mode),
31
+ * returns the identity function — zero overhead.
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * function ShellCommand({ command }: { command: string }) {
36
+ * const normalize = useSandboxNormalize();
37
+ * return <code>{normalize(command)}</code>;
38
+ * }
39
+ * ```
40
+ */
41
+ export function useSandboxNormalize(): (text: string) => string {
42
+ const { sandboxWorkspaceRoot } = useContext(SandboxContext);
43
+ return useCallback(
44
+ (text: string) => normalizeSandboxPaths(text, sandboxWorkspaceRoot),
45
+ [sandboxWorkspaceRoot],
46
+ );
47
+ }
@@ -7,14 +7,25 @@ import { cn } from "@stigmer/theme";
7
7
  export interface SetupProgressProps {
8
8
  /**
9
9
  * Workspace entries from the session spec. When git-sourced entries
10
- * are present, the indicator shows workspace-specific messaging
11
- * (e.g. "Setting up workspace...") to match the backend's actual
12
- * setup sequence.
10
+ * are present, the fallback indicator shows workspace-specific messaging
11
+ * (e.g. "Setting up workspace...").
13
12
  */
14
13
  readonly workspaceEntries?: readonly WorkspaceEntry[];
14
+ /**
15
+ * Server-reported setup phase label from
16
+ * `AgentExecutionStatus.setup_progress.current_phase`.
17
+ *
18
+ * When non-empty, rendered directly — the timer-based fallback is
19
+ * bypassed. When absent or empty (older backends that don't emit
20
+ * setup progress), the component falls back to the time-based
21
+ * step sequence derived from `workspaceEntries`.
22
+ */
23
+ readonly serverPhase?: string;
15
24
  readonly className?: string;
16
25
  }
17
26
 
27
+ /* ── Timer-based fallback (used when no serverPhase is available) ─── */
28
+
18
29
  interface SetupStep {
19
30
  readonly message: string;
20
31
  readonly durationMs: number;
@@ -55,28 +66,33 @@ function buildSteps(
55
66
  * Animated inline indicator shown in the message thread while an
56
67
  * execution is in the `PENDING` phase and no AI messages have arrived.
57
68
  *
58
- * Cycles through contextual status messages derived from the session
59
- * configuration (workspace entries, etc.) to communicate that the
60
- * backend is actively setting up the sandbox, cloning repositories,
61
- * merging environment variables, loading skills, and connecting MCP
62
- * servers.
69
+ * **Server-driven mode** when the backend reports
70
+ * `setup_progress.current_phase` through the execution stream, the
71
+ * component renders the server-reported label directly.
63
72
  *
64
- * Uses time-based progression that approximates the backend's actual
65
- * setup sequence. A future backend-enriched phase (surfacing real
66
- * setup phase labels through the execution stream) can replace the
67
- * timer with server-reported progress.
73
+ * **Timer-based fallback** when `serverPhase` is absent (older
74
+ * backends), the component cycles through contextual status messages
75
+ * derived from the session configuration on a fixed interval.
68
76
  *
69
77
  * All visual properties flow through `--stgm-*` tokens.
70
78
  *
71
79
  * @example
72
80
  * ```tsx
81
+ * // Server-driven (preferred)
82
+ * <SetupProgress serverPhase={execution.status?.setupProgress?.currentPhase} />
83
+ *
84
+ * // Fallback (no server phase available)
73
85
  * <SetupProgress workspaceEntries={session.spec?.workspaceEntries} />
74
86
  * ```
75
87
  */
76
88
  export function SetupProgress({
77
89
  workspaceEntries,
90
+ serverPhase,
78
91
  className,
79
92
  }: SetupProgressProps) {
93
+ const useServerPhase = !!serverPhase;
94
+
95
+ /* ── Timer-based fallback state ─────────────────────────────────── */
80
96
  const steps = useMemo(() => buildSteps(workspaceEntries), [workspaceEntries]);
81
97
  const [stepIndex, setStepIndex] = useState(0);
82
98
 
@@ -85,6 +101,7 @@ export function SetupProgress({
85
101
  }, [steps]);
86
102
 
87
103
  useEffect(() => {
104
+ if (useServerPhase) return;
88
105
  if (stepIndex >= steps.length - 1) return;
89
106
 
90
107
  const timer = setTimeout(() => {
@@ -92,9 +109,11 @@ export function SetupProgress({
92
109
  }, steps[stepIndex].durationMs);
93
110
 
94
111
  return () => clearTimeout(timer);
95
- }, [stepIndex, steps]);
112
+ }, [useServerPhase, stepIndex, steps]);
96
113
 
97
- const currentMessage = steps[Math.min(stepIndex, steps.length - 1)].message;
114
+ /* ── Resolve display message ────────────────────────────────────── */
115
+ const currentMessage =
116
+ serverPhase || steps[Math.min(stepIndex, steps.length - 1)].message;
98
117
 
99
118
  return (
100
119
  <div
@@ -9,6 +9,7 @@ import {
9
9
  import type { ToolCategory, ToolCategoryInfo } from "./tool-categories";
10
10
  import { FilePathLink } from "./FilePathLink";
11
11
  import { McpArgsView, McpMetadataRow } from "./McpToolDetail";
12
+ import { useSandboxNormalize } from "./SandboxContext";
12
13
  import {
13
14
  CollapsibleCode,
14
15
  FilePathIcon,
@@ -142,13 +143,14 @@ function CategoryArgsDispatch({
142
143
  // ---------------------------------------------------------------------------
143
144
 
144
145
  function ShellArgsView({ command }: { command: string }) {
146
+ const normalize = useSandboxNormalize();
145
147
  return (
146
148
  <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
147
149
  <pre className="whitespace-pre-wrap break-words font-mono text-xs text-[var(--stgm-terminal-fg,#e0e0e0)]">
148
150
  <span className="select-none text-[var(--stgm-terminal-prompt,#6b7280)]">
149
151
  ${" "}
150
152
  </span>
151
- {command}
153
+ {normalize(command)}
152
154
  </pre>
153
155
  </div>
154
156
  );
@@ -5,6 +5,7 @@ import { ToolCallStatus } from "@stigmer/protos/ai/stigmer/agentic/agentexecutio
5
5
  import { cn } from "@stigmer/theme";
6
6
  import { resolveToolCategory } from "./tool-categories";
7
7
  import { McpToolDetail } from "./McpToolDetail";
8
+ import { useSandboxNormalize } from "./SandboxContext";
8
9
  import { ToolArgsView } from "./ToolArgsView";
9
10
  import {
10
11
  CollapsibleCode,
@@ -105,6 +106,7 @@ function CategoryRenderer({
105
106
 
106
107
  function ShellToolDetail({ toolCall }: { toolCall: ToolCall }) {
107
108
  const duration = formatDuration(toolCall.startedAt, toolCall.completedAt);
109
+ const normalize = useSandboxNormalize();
108
110
 
109
111
  return (
110
112
  <>
@@ -121,7 +123,7 @@ function ShellToolDetail({ toolCall }: { toolCall: ToolCall }) {
121
123
  <span className="font-medium text-muted-foreground">Output</span>
122
124
  <div className="rounded-md border border-border bg-[var(--stgm-terminal-bg,#1a1a2e)] p-2.5">
123
125
  <CollapsiblePre
124
- content={toolCall.result}
126
+ content={normalize(toolCall.result)}
125
127
  className="text-[var(--stgm-terminal-fg,#e0e0e0)]"
126
128
  />
127
129
  </div>
@@ -16,6 +16,7 @@ import {
16
16
  extractPrimaryArg,
17
17
  type ToolCategory,
18
18
  } from "./tool-categories";
19
+ import { useSandboxNormalize } from "./SandboxContext";
19
20
 
20
21
  export interface ToolCallItemProps {
21
22
  readonly toolCall: ToolCall;
@@ -74,6 +75,7 @@ export function ToolCallItem({
74
75
  ? subAgentExecution.subject || subAgentExecution.name || categoryInfo.label
75
76
  : categoryInfo.label;
76
77
 
78
+ const normalize = useSandboxNormalize();
77
79
  const approvalBadge = getApprovalBadge(toolCall);
78
80
 
79
81
  // Completed/skipped Read items are non-expandable — the clickable
@@ -144,7 +146,11 @@ export function ToolCallItem({
144
146
  );
145
147
  }
146
148
 
147
- const displaySubtitle = isSubAgent ? null : primaryArg;
149
+ const displaySubtitle = isSubAgent
150
+ ? null
151
+ : categoryInfo.category === "shell" && primaryArg
152
+ ? normalize(primaryArg)
153
+ : primaryArg;
148
154
 
149
155
  return (
150
156
  <div className={cn("border-b border-border/50 last:border-b-0", className)}>