@stigmer/react 3.6.0 → 3.7.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.
- package/billing/CreditLedgerTable.d.ts.map +1 -1
- package/billing/CreditLedgerTable.js +2 -7
- package/billing/CreditLedgerTable.js.map +1 -1
- package/composer/ComposerToolbar.d.ts +6 -1
- package/composer/ComposerToolbar.d.ts.map +1 -1
- package/composer/ComposerToolbar.js +2 -2
- package/composer/ComposerToolbar.js.map +1 -1
- package/composer/SessionComposer.d.ts +9 -0
- package/composer/SessionComposer.d.ts.map +1 -1
- package/composer/SessionComposer.js +13 -2
- package/composer/SessionComposer.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +14 -0
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +5 -0
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +2 -2
- package/index.d.ts.map +1 -1
- package/index.js +6 -5
- package/index.js.map +1 -1
- package/internal/Pagination.d.ts +15 -0
- package/internal/Pagination.d.ts.map +1 -0
- package/internal/Pagination.js +20 -0
- package/internal/Pagination.js.map +1 -0
- package/models/ModelSelector.d.ts +26 -1
- package/models/ModelSelector.d.ts.map +1 -1
- package/models/ModelSelector.js +27 -6
- package/models/ModelSelector.js.map +1 -1
- package/models/index.d.ts +2 -0
- package/models/index.d.ts.map +1 -1
- package/models/index.js +1 -0
- package/models/index.js.map +1 -1
- package/models/registry.d.ts +10 -0
- package/models/registry.d.ts.map +1 -1
- package/models/registry.js +3 -0
- package/models/registry.js.map +1 -1
- package/models/service-tier.d.ts +27 -0
- package/models/service-tier.d.ts.map +1 -0
- package/models/service-tier.js +34 -0
- package/models/service-tier.js.map +1 -0
- package/package.json +4 -4
- package/schedule/ScheduleDetailView.d.ts +31 -8
- package/schedule/ScheduleDetailView.d.ts.map +1 -1
- package/schedule/ScheduleDetailView.js +441 -29
- package/schedule/ScheduleDetailView.js.map +1 -1
- package/schedule/ScheduleForm.d.ts +7 -4
- package/schedule/ScheduleForm.d.ts.map +1 -1
- package/schedule/ScheduleForm.js +93 -8
- package/schedule/ScheduleForm.js.map +1 -1
- package/schedule/ScheduleRunsTable.d.ts +72 -0
- package/schedule/ScheduleRunsTable.d.ts.map +1 -0
- package/schedule/ScheduleRunsTable.js +161 -0
- package/schedule/ScheduleRunsTable.js.map +1 -0
- package/schedule/index.d.ts +6 -0
- package/schedule/index.d.ts.map +1 -1
- package/schedule/index.js +3 -0
- package/schedule/index.js.map +1 -1
- package/schedule/useScheduleRuns.d.ts +44 -0
- package/schedule/useScheduleRuns.d.ts.map +1 -0
- package/schedule/useScheduleRuns.js +48 -0
- package/schedule/useScheduleRuns.js.map +1 -0
- package/schedule/useTriggerSchedule.d.ts +23 -10
- package/schedule/useTriggerSchedule.d.ts.map +1 -1
- package/schedule/useTriggerSchedule.js +28 -9
- package/schedule/useTriggerSchedule.js.map +1 -1
- package/schedule/useUpdateScheduleSpec.d.ts +49 -0
- package/schedule/useUpdateScheduleSpec.d.ts.map +1 -0
- package/schedule/useUpdateScheduleSpec.js +70 -0
- package/schedule/useUpdateScheduleSpec.js.map +1 -0
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +1 -0
- package/session/useNewSessionFlow.js.map +1 -1
- package/session/useSessionConversation.d.ts +11 -0
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +1 -0
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionPageFlow.d.ts.map +1 -1
- package/session/useSessionPageFlow.js +1 -0
- package/session/useSessionPageFlow.js.map +1 -1
- package/src/billing/CreditLedgerTable.tsx +3 -42
- package/src/composer/ComposerToolbar.tsx +9 -0
- package/src/composer/SessionComposer.tsx +24 -1
- package/src/composer/__tests__/SessionComposer-serviceTier.test.tsx +156 -0
- package/src/execution/useCreateAgentExecution.ts +18 -0
- package/src/index.ts +9 -4
- package/src/internal/Pagination.tsx +72 -0
- package/src/models/ModelSelector.tsx +100 -3
- package/src/models/__tests__/ModelSelector-serviceTier.test.tsx +172 -0
- package/src/models/__tests__/useModelRegistry.test.tsx +15 -0
- package/src/models/index.ts +2 -0
- package/src/models/registry.ts +16 -0
- package/src/models/service-tier.ts +49 -0
- package/src/schedule/ScheduleDetailView.tsx +1179 -111
- package/src/schedule/ScheduleForm.tsx +217 -7
- package/src/schedule/ScheduleRunsTable.tsx +424 -0
- package/src/schedule/__tests__/ScheduleDetailView.test.tsx +431 -8
- package/src/schedule/__tests__/scheduleCreation.test.tsx +197 -0
- package/src/schedule/__tests__/scheduleHooks.test.tsx +94 -7
- package/src/schedule/index.ts +12 -0
- package/src/schedule/useScheduleRuns.ts +93 -0
- package/src/schedule/useTriggerSchedule.ts +39 -14
- package/src/schedule/useUpdateScheduleSpec.ts +108 -0
- package/src/session/useNewSessionFlow.ts +1 -0
- package/src/session/useSessionConversation.ts +12 -0
- package/src/session/useSessionPageFlow.ts +1 -0
- package/src/workflow/__tests__/inspector-forms.test.tsx +119 -2
- package/src/workflow/inspector/forms/AgentCallForm.tsx +260 -37
- package/src/workflow/inspector/tabs/RuntimeTab.tsx +72 -33
- package/src/workflow/starter-workflow-yaml.ts +2 -1
- package/styles.css +1 -1
- package/workflow/inspector/forms/AgentCallForm.d.ts +18 -2
- package/workflow/inspector/forms/AgentCallForm.d.ts.map +1 -1
- package/workflow/inspector/forms/AgentCallForm.js +87 -9
- package/workflow/inspector/forms/AgentCallForm.js.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts +2 -1
- package/workflow/inspector/tabs/RuntimeTab.d.ts.map +1 -1
- package/workflow/inspector/tabs/RuntimeTab.js +31 -5
- package/workflow/inspector/tabs/RuntimeTab.js.map +1 -1
- package/workflow/starter-workflow-yaml.d.ts +1 -1
- package/workflow/starter-workflow-yaml.d.ts.map +1 -1
- package/workflow/starter-workflow-yaml.js +2 -1
- package/workflow/starter-workflow-yaml.js.map +1 -1
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
WorkspaceEntryInput,
|
|
18
18
|
} from "@stigmer/sdk";
|
|
19
19
|
import { isTerminalPhase } from "../execution/execution-phases.js";
|
|
20
|
+
import type { ServiceTierOption } from "../models/service-tier.js";
|
|
20
21
|
import { useStigmer } from "../hooks.js";
|
|
21
22
|
import { toError } from "../internal/toError.js";
|
|
22
23
|
import { useConversationStoreRef } from "../internal/store/index.js";
|
|
@@ -96,6 +97,16 @@ export interface SendFollowUpOptions {
|
|
|
96
97
|
* - `"plan"`: read-only analysis, no file mutations.
|
|
97
98
|
*/
|
|
98
99
|
readonly interactionMode?: "agent" | "plan";
|
|
100
|
+
/**
|
|
101
|
+
* Service tier for this execution's model calls (stigmer/stigmer#357).
|
|
102
|
+
*
|
|
103
|
+
* Only set when the user actively selected `"fast"`; `undefined` preserves
|
|
104
|
+
* the unspecified-vs-explicit distinction (unset resolves to standard on
|
|
105
|
+
* the platform side — never the provider account default).
|
|
106
|
+
*
|
|
107
|
+
* @see {@link CreateAgentExecutionInput.serviceTier}
|
|
108
|
+
*/
|
|
109
|
+
readonly serviceTier?: ServiceTierOption;
|
|
99
110
|
/**
|
|
100
111
|
* Marks this execution as a Build-from-plan turn.
|
|
101
112
|
*
|
|
@@ -642,6 +653,7 @@ export function useSessionConversation(
|
|
|
642
653
|
runtimeEnv: options?.runtimeEnv,
|
|
643
654
|
attachments: options?.attachments,
|
|
644
655
|
interactionMode: options?.interactionMode,
|
|
656
|
+
serviceTier: options?.serviceTier,
|
|
645
657
|
buildFromPlan: options?.buildFromPlan,
|
|
646
658
|
autoApproveAll: options?.autoApproveAll,
|
|
647
659
|
workspaceFileRefs: options?.workspaceFileRefs,
|
|
@@ -438,6 +438,7 @@ export function useSessionPageFlow(
|
|
|
438
438
|
runtimeEnv,
|
|
439
439
|
attachments: context?.attachments,
|
|
440
440
|
interactionMode: context?.interactionMode,
|
|
441
|
+
serviceTier: context?.serviceTier,
|
|
441
442
|
buildFromPlan: context?.buildFromPlan,
|
|
442
443
|
// Sourced from the session-scoped preference set at the approval gate,
|
|
443
444
|
// not from the composer (the pre-arm toggle was removed).
|
|
@@ -34,15 +34,132 @@ describe("AgentCallForm", () => {
|
|
|
34
34
|
expect(textarea.value).toBe("Hello world");
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it("renders model input from
|
|
37
|
+
it("renders model input from the run_config block", () => {
|
|
38
38
|
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
39
39
|
agent: "x",
|
|
40
40
|
message: "y",
|
|
41
|
-
|
|
41
|
+
run_config: { model_name: "claude-sonnet-4", max_cost_usd: 0.5 },
|
|
42
42
|
});
|
|
43
43
|
render(<AgentCallForm node={node} onFieldChange={vi.fn()} />);
|
|
44
44
|
const input = screen.getByTestId("agent-call-model-input") as HTMLInputElement;
|
|
45
45
|
expect(input.value).toBe("claude-sonnet-4");
|
|
46
|
+
const budget = screen.getByTestId("agent-call-budget-input") as HTMLInputElement;
|
|
47
|
+
expect(budget.value).toBe("0.5");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("emits an undefined run_config when the last field is cleared", () => {
|
|
51
|
+
const onFieldChange = vi.fn();
|
|
52
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
53
|
+
agent: "x",
|
|
54
|
+
message: "y",
|
|
55
|
+
run_config: { max_cost_usd: 0.5 },
|
|
56
|
+
});
|
|
57
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
58
|
+
|
|
59
|
+
const budget = screen.getByTestId("agent-call-budget-input") as HTMLInputElement;
|
|
60
|
+
fireEvent.change(budget, { target: { value: "" } });
|
|
61
|
+
|
|
62
|
+
// Empty means omit: a blank field must not persist a zero override.
|
|
63
|
+
expect(onFieldChange).toHaveBeenCalledWith("run_config", undefined);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("writes budget changes into run_config.max_cost_usd", () => {
|
|
67
|
+
const onFieldChange = vi.fn();
|
|
68
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
69
|
+
agent: "x",
|
|
70
|
+
message: "y",
|
|
71
|
+
run_config: { model_name: "claude-sonnet-4" },
|
|
72
|
+
});
|
|
73
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
74
|
+
|
|
75
|
+
const budget = screen.getByTestId("agent-call-budget-input") as HTMLInputElement;
|
|
76
|
+
fireEvent.change(budget, { target: { value: "1.25" } });
|
|
77
|
+
|
|
78
|
+
expect(onFieldChange).toHaveBeenCalledWith("run_config", {
|
|
79
|
+
model_name: "claude-sonnet-4",
|
|
80
|
+
max_cost_usd: 1.25,
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("renders workspace entries and edits the git url in place", () => {
|
|
85
|
+
const onFieldChange = vi.fn();
|
|
86
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
87
|
+
agent: "x",
|
|
88
|
+
message: "y",
|
|
89
|
+
workspace_entries: [
|
|
90
|
+
{ name: "app", source: { git_repo: { url: "https://github.com/acme/app", branch: "main" } } },
|
|
91
|
+
],
|
|
92
|
+
});
|
|
93
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
94
|
+
|
|
95
|
+
const url = screen.getByTestId("agent-call-workspace-url-0") as HTMLInputElement;
|
|
96
|
+
expect(url.value).toBe("https://github.com/acme/app");
|
|
97
|
+
|
|
98
|
+
fireEvent.change(url, { target: { value: "https://github.com/acme/other" } });
|
|
99
|
+
expect(onFieldChange).toHaveBeenCalledWith("workspace_entries", [
|
|
100
|
+
{ name: "app", source: { git_repo: { url: "https://github.com/acme/other", branch: "main" } } },
|
|
101
|
+
]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("adds a workspace entry row", () => {
|
|
105
|
+
const onFieldChange = vi.fn();
|
|
106
|
+
const node = makeNode(WorkflowTaskKind.agent_call, { agent: "x", message: "y" });
|
|
107
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
108
|
+
|
|
109
|
+
fireEvent.click(screen.getByTestId("agent-call-workspace-add"));
|
|
110
|
+
expect(onFieldChange).toHaveBeenCalledWith("workspace_entries", [
|
|
111
|
+
{ source: { git_repo: { url: "" } } },
|
|
112
|
+
]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("emits undefined when the last workspace entry is removed", () => {
|
|
116
|
+
const onFieldChange = vi.fn();
|
|
117
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
118
|
+
agent: "x",
|
|
119
|
+
message: "y",
|
|
120
|
+
workspace_entries: [
|
|
121
|
+
{ source: { git_repo: { url: "https://github.com/acme/app" } } },
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
125
|
+
|
|
126
|
+
fireEvent.click(screen.getByLabelText("Remove workspace entry 1"));
|
|
127
|
+
expect(onFieldChange).toHaveBeenCalledWith("workspace_entries", undefined);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("edits environment references as org/slug rows with empty-org omitted", () => {
|
|
131
|
+
const onFieldChange = vi.fn();
|
|
132
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
133
|
+
agent: "x",
|
|
134
|
+
message: "y",
|
|
135
|
+
environment_refs: [{ slug: "shared-secrets" }],
|
|
136
|
+
});
|
|
137
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
138
|
+
|
|
139
|
+
const slug = screen.getByTestId("agent-call-envref-slug-0") as HTMLInputElement;
|
|
140
|
+
expect(slug.value).toBe("shared-secrets");
|
|
141
|
+
|
|
142
|
+
fireEvent.change(slug, { target: { value: "other-secrets" } });
|
|
143
|
+
// Empty org stays omitted — an empty string must not become a stored
|
|
144
|
+
// org override (relative refs resolve to the workflow's own org).
|
|
145
|
+
expect(onFieldChange).toHaveBeenCalledWith("environment_refs", [
|
|
146
|
+
{ slug: "other-secrets" },
|
|
147
|
+
]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("adds and removes environment reference rows", () => {
|
|
151
|
+
const onFieldChange = vi.fn();
|
|
152
|
+
const node = makeNode(WorkflowTaskKind.agent_call, {
|
|
153
|
+
agent: "x",
|
|
154
|
+
message: "y",
|
|
155
|
+
environment_refs: [{ slug: "a" }, { org: "acme", slug: "b" }],
|
|
156
|
+
});
|
|
157
|
+
render(<AgentCallForm node={node} onFieldChange={onFieldChange} />);
|
|
158
|
+
|
|
159
|
+
fireEvent.click(screen.getByLabelText("Remove environment reference 1"));
|
|
160
|
+
expect(onFieldChange).toHaveBeenCalledWith("environment_refs", [
|
|
161
|
+
{ org: "acme", slug: "b" },
|
|
162
|
+
]);
|
|
46
163
|
});
|
|
47
164
|
|
|
48
165
|
it("shows structured output section when output is present", () => {
|
|
@@ -17,8 +17,24 @@ const inputClass =
|
|
|
17
17
|
* Specialized configuration form for `agent_call` tasks.
|
|
18
18
|
*
|
|
19
19
|
* Organizes agent-specific fields into semantic sections: Agent identity,
|
|
20
|
-
* harness selection, message prompt, environment variables,
|
|
21
|
-
*
|
|
20
|
+
* harness selection, message prompt, environment variables, run config
|
|
21
|
+
* (model, budget per run), and structured output.
|
|
22
|
+
*
|
|
23
|
+
* The run config edits the shared `run_config` block (the
|
|
24
|
+
* agentexecution RunConfig vocabulary, stigmer/stigmer#358): model
|
|
25
|
+
* override plus a per-run USD budget that the runner actually
|
|
26
|
+
* enforces. `max_tool_rounds` is API-reachable but deliberately absent
|
|
27
|
+
* from the form (DD-018 D-5: an implementation knob, not a user
|
|
28
|
+
* concept). Assembly follows the schedule form's empty-means-omit rule:
|
|
29
|
+
* a blank field must not become a zero override.
|
|
30
|
+
*
|
|
31
|
+
* Workspace and environments edit the shared vocabulary's
|
|
32
|
+
* `workspace_entries` (git-only at this surface) and `environment_refs`
|
|
33
|
+
* as controlled structured rows. The schedule form's richer components
|
|
34
|
+
* (WorkspaceEditor with the GitHub repo picker, EnvironmentPicker) are
|
|
35
|
+
* data-connected and need org context this inspector tree does not
|
|
36
|
+
* carry yet — adopting them here is a named follow-up, not a silent
|
|
37
|
+
* divergence.
|
|
22
38
|
*
|
|
23
39
|
* Composes primitive field controls rather than rendering a generic
|
|
24
40
|
* schema form — following the research report guidance that "the forms
|
|
@@ -31,7 +47,7 @@ export const AgentCallForm = memo(function AgentCallForm({
|
|
|
31
47
|
onFieldChange,
|
|
32
48
|
}: AgentCallFormProps) {
|
|
33
49
|
const config = node.config as Record<string, unknown>;
|
|
34
|
-
const
|
|
50
|
+
const runConfig = (config.run_config ?? {}) as Record<string, unknown>;
|
|
35
51
|
const outputContract = (config.output ?? {}) as Record<string, unknown>;
|
|
36
52
|
const hasOutput = Object.keys(outputContract).length > 0;
|
|
37
53
|
const [showOutput, setShowOutput] = useState(hasOutput);
|
|
@@ -43,14 +59,18 @@ export const AgentCallForm = memo(function AgentCallForm({
|
|
|
43
59
|
[onFieldChange],
|
|
44
60
|
);
|
|
45
61
|
|
|
46
|
-
const
|
|
62
|
+
const handleRunConfigChange = useCallback(
|
|
47
63
|
(field: string, value: unknown) => {
|
|
48
|
-
|
|
49
|
-
...
|
|
64
|
+
const next: Record<string, unknown> = {
|
|
65
|
+
...runConfig,
|
|
50
66
|
[field]: value === "" ? undefined : value,
|
|
51
|
-
}
|
|
67
|
+
};
|
|
68
|
+
for (const key of Object.keys(next)) {
|
|
69
|
+
if (next[key] === undefined) delete next[key];
|
|
70
|
+
}
|
|
71
|
+
onFieldChange("run_config", Object.keys(next).length > 0 ? next : undefined);
|
|
52
72
|
},
|
|
53
|
-
[
|
|
73
|
+
[runConfig, onFieldChange],
|
|
54
74
|
);
|
|
55
75
|
|
|
56
76
|
const handleOutputChange = useCallback(
|
|
@@ -165,54 +185,45 @@ export const AgentCallForm = memo(function AgentCallForm({
|
|
|
165
185
|
</section>
|
|
166
186
|
)}
|
|
167
187
|
|
|
168
|
-
{/*
|
|
188
|
+
{/* Run config */}
|
|
169
189
|
<section className="flex flex-col gap-2">
|
|
170
190
|
<SectionLabel>Execution</SectionLabel>
|
|
171
191
|
<FieldRow label="Model" hint="Override the agent's default model">
|
|
172
192
|
<input
|
|
173
193
|
type="text"
|
|
174
|
-
value={typeof
|
|
175
|
-
onChange={(e) =>
|
|
194
|
+
value={typeof runConfig.model_name === "string" ? runConfig.model_name : ""}
|
|
195
|
+
onChange={(e) => handleRunConfigChange("model_name", e.target.value)}
|
|
176
196
|
placeholder="Agent default"
|
|
177
197
|
className={inputClass}
|
|
178
198
|
data-testid="agent-call-model-input"
|
|
179
199
|
/>
|
|
180
200
|
</FieldRow>
|
|
181
|
-
<FieldRow label="
|
|
182
|
-
<input
|
|
183
|
-
type="number"
|
|
184
|
-
value={typeof execConfig.timeout === "number" ? execConfig.timeout : ""}
|
|
185
|
-
onChange={(e) => handleExecConfigChange("timeout", e.target.value ? parseInt(e.target.value, 10) : undefined)}
|
|
186
|
-
placeholder="300"
|
|
187
|
-
min={1}
|
|
188
|
-
max={3600}
|
|
189
|
-
className={inputClass}
|
|
190
|
-
/>
|
|
191
|
-
</FieldRow>
|
|
192
|
-
<FieldRow label="Temperature" hint="0.0 (deterministic) to 1.0 (creative)">
|
|
193
|
-
<input
|
|
194
|
-
type="number"
|
|
195
|
-
value={typeof execConfig.temperature === "number" ? execConfig.temperature : ""}
|
|
196
|
-
onChange={(e) => handleExecConfigChange("temperature", e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
197
|
-
placeholder="0.7"
|
|
198
|
-
min={0}
|
|
199
|
-
max={1}
|
|
200
|
-
step={0.1}
|
|
201
|
-
className={inputClass}
|
|
202
|
-
/>
|
|
203
|
-
</FieldRow>
|
|
204
|
-
<FieldRow label="Max cost" hint="Per-task cap in micro-USD (1 USD = 1,000,000)">
|
|
201
|
+
<FieldRow label="Budget per run (USD)" hint="The call stops when its estimated cost reaches this amount">
|
|
205
202
|
<input
|
|
206
203
|
type="number"
|
|
207
|
-
value={typeof
|
|
208
|
-
onChange={(e) =>
|
|
204
|
+
value={typeof runConfig.max_cost_usd === "number" ? runConfig.max_cost_usd : ""}
|
|
205
|
+
onChange={(e) => handleRunConfigChange("max_cost_usd", e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
209
206
|
placeholder="No limit"
|
|
210
207
|
min={0}
|
|
208
|
+
step={0.05}
|
|
211
209
|
className={inputClass}
|
|
210
|
+
data-testid="agent-call-budget-input"
|
|
212
211
|
/>
|
|
213
212
|
</FieldRow>
|
|
214
213
|
</section>
|
|
215
214
|
|
|
215
|
+
{/* Workspace (git-only at this surface) */}
|
|
216
|
+
<WorkspaceEntriesSection
|
|
217
|
+
entries={config.workspace_entries}
|
|
218
|
+
onChange={(entries) => onFieldChange("workspace_entries", entries)}
|
|
219
|
+
/>
|
|
220
|
+
|
|
221
|
+
{/* Environments */}
|
|
222
|
+
<EnvironmentRefsSection
|
|
223
|
+
refs={config.environment_refs}
|
|
224
|
+
onChange={(refs) => onFieldChange("environment_refs", refs)}
|
|
225
|
+
/>
|
|
226
|
+
|
|
216
227
|
{/* Structured output */}
|
|
217
228
|
<section className="flex flex-col gap-2">
|
|
218
229
|
<div className="flex items-center gap-2">
|
|
@@ -278,6 +289,218 @@ export const AgentCallForm = memo(function AgentCallForm({
|
|
|
278
289
|
);
|
|
279
290
|
});
|
|
280
291
|
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
// Workspace entries (git-only structured rows)
|
|
294
|
+
// ---------------------------------------------------------------------------
|
|
295
|
+
|
|
296
|
+
interface WorkspaceEntryRow {
|
|
297
|
+
readonly name?: string;
|
|
298
|
+
readonly source?: { readonly git_repo?: { readonly url?: string; readonly branch?: string } };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Edits `workspace_entries` as structured URL + branch rows. Only git
|
|
303
|
+
* sources exist at this surface (write-time validation rejects
|
|
304
|
+
* local_path — no client is connected when a workflow task fires), so
|
|
305
|
+
* the row model is deliberately git-shaped. Private repos need an
|
|
306
|
+
* org-visibility Environment holding GITHUB_TOKEN bound under
|
|
307
|
+
* Environments below (DD-018 D-4).
|
|
308
|
+
*/
|
|
309
|
+
function WorkspaceEntriesSection({
|
|
310
|
+
entries,
|
|
311
|
+
onChange,
|
|
312
|
+
}: {
|
|
313
|
+
entries: unknown;
|
|
314
|
+
onChange: (entries: WorkspaceEntryRow[] | undefined) => void;
|
|
315
|
+
}) {
|
|
316
|
+
const rows = useMemo<readonly WorkspaceEntryRow[]>(
|
|
317
|
+
() => (Array.isArray(entries) ? (entries as WorkspaceEntryRow[]) : []),
|
|
318
|
+
[entries],
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
const commit = useCallback(
|
|
322
|
+
(next: WorkspaceEntryRow[]) => {
|
|
323
|
+
onChange(next.length > 0 ? next : undefined);
|
|
324
|
+
},
|
|
325
|
+
[onChange],
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
const updateRow = useCallback(
|
|
329
|
+
(index: number, url: string, branch: string) => {
|
|
330
|
+
const next = rows.map((row, i) => {
|
|
331
|
+
if (i !== index) return row;
|
|
332
|
+
const gitRepo: { url: string; branch?: string } = { url };
|
|
333
|
+
if (branch) gitRepo.branch = branch;
|
|
334
|
+
return { ...(row.name ? { name: row.name } : {}), source: { git_repo: gitRepo } };
|
|
335
|
+
});
|
|
336
|
+
commit([...next]);
|
|
337
|
+
},
|
|
338
|
+
[rows, commit],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<section className="flex flex-col gap-2">
|
|
343
|
+
<div className="flex items-center justify-between">
|
|
344
|
+
<SectionLabel>Workspace</SectionLabel>
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
onClick={() => commit([...rows, { source: { git_repo: { url: "" } } }])}
|
|
348
|
+
className="text-[10px] font-medium text-[var(--stgm-primary,#6366f1)]"
|
|
349
|
+
data-testid="agent-call-workspace-add"
|
|
350
|
+
>
|
|
351
|
+
+ Add repository
|
|
352
|
+
</button>
|
|
353
|
+
</div>
|
|
354
|
+
{rows.length === 0 ? (
|
|
355
|
+
<p className="text-[10px] leading-tight text-[var(--stgm-muted-foreground,#737373)]">
|
|
356
|
+
Git repositories the agent works on. Private repos need an org-shared
|
|
357
|
+
environment holding GITHUB_TOKEN bound under Environments.
|
|
358
|
+
</p>
|
|
359
|
+
) : (
|
|
360
|
+
<div className="flex flex-col gap-1.5">
|
|
361
|
+
{rows.map((row, i) => {
|
|
362
|
+
const gitRepo = row.source?.git_repo ?? {};
|
|
363
|
+
return (
|
|
364
|
+
<div key={i} className="flex items-start gap-1">
|
|
365
|
+
<input
|
|
366
|
+
type="text"
|
|
367
|
+
value={gitRepo.url ?? ""}
|
|
368
|
+
onChange={(e) => updateRow(i, e.target.value, gitRepo.branch ?? "")}
|
|
369
|
+
placeholder="https://github.com/org/repo"
|
|
370
|
+
className={`${inputClass} flex-1`}
|
|
371
|
+
data-testid={`agent-call-workspace-url-${i}`}
|
|
372
|
+
/>
|
|
373
|
+
<input
|
|
374
|
+
type="text"
|
|
375
|
+
value={gitRepo.branch ?? ""}
|
|
376
|
+
onChange={(e) => updateRow(i, gitRepo.url ?? "", e.target.value)}
|
|
377
|
+
placeholder="branch"
|
|
378
|
+
className={`${inputClass} w-1/4`}
|
|
379
|
+
data-testid={`agent-call-workspace-branch-${i}`}
|
|
380
|
+
/>
|
|
381
|
+
<button
|
|
382
|
+
type="button"
|
|
383
|
+
onClick={() => commit(rows.filter((_, j) => j !== i))}
|
|
384
|
+
aria-label={`Remove workspace entry ${i + 1}`}
|
|
385
|
+
className="mt-1 text-[var(--stgm-muted-foreground,#737373)] hover:text-[var(--stgm-destructive,#ef4444)]"
|
|
386
|
+
>
|
|
387
|
+
×
|
|
388
|
+
</button>
|
|
389
|
+
</div>
|
|
390
|
+
);
|
|
391
|
+
})}
|
|
392
|
+
</div>
|
|
393
|
+
)}
|
|
394
|
+
</section>
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
// Environment references (structured rows)
|
|
400
|
+
// ---------------------------------------------------------------------------
|
|
401
|
+
|
|
402
|
+
interface EnvironmentRefRow {
|
|
403
|
+
readonly org?: string;
|
|
404
|
+
readonly slug?: string;
|
|
405
|
+
readonly kind?: string;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Edits `environment_refs` as slug rows (org optional — empty means the
|
|
410
|
+
* workflow's own org). These are how a tool-using agent becomes callable
|
|
411
|
+
* from a workflow: the task binds the credentials its child runs need
|
|
412
|
+
* without touching the agent. Resolved server-side at execution create;
|
|
413
|
+
* an unresolvable ref fails the run closed.
|
|
414
|
+
*/
|
|
415
|
+
function EnvironmentRefsSection({
|
|
416
|
+
refs,
|
|
417
|
+
onChange,
|
|
418
|
+
}: {
|
|
419
|
+
refs: unknown;
|
|
420
|
+
onChange: (refs: EnvironmentRefRow[] | undefined) => void;
|
|
421
|
+
}) {
|
|
422
|
+
const rows = useMemo<readonly EnvironmentRefRow[]>(
|
|
423
|
+
() => (Array.isArray(refs) ? (refs as EnvironmentRefRow[]) : []),
|
|
424
|
+
[refs],
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const commit = useCallback(
|
|
428
|
+
(next: EnvironmentRefRow[]) => {
|
|
429
|
+
onChange(next.length > 0 ? next : undefined);
|
|
430
|
+
},
|
|
431
|
+
[onChange],
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
const updateRow = useCallback(
|
|
435
|
+
(index: number, field: "org" | "slug", value: string) => {
|
|
436
|
+
const next = rows.map((row, i) => {
|
|
437
|
+
if (i !== index) return row;
|
|
438
|
+
const updated: Record<string, string> = {};
|
|
439
|
+
const org = field === "org" ? value : row.org ?? "";
|
|
440
|
+
const slug = field === "slug" ? value : row.slug ?? "";
|
|
441
|
+
if (org) updated.org = org;
|
|
442
|
+
updated.slug = slug;
|
|
443
|
+
return updated as EnvironmentRefRow;
|
|
444
|
+
});
|
|
445
|
+
commit([...next]);
|
|
446
|
+
},
|
|
447
|
+
[rows, commit],
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
return (
|
|
451
|
+
<section className="flex flex-col gap-2">
|
|
452
|
+
<div className="flex items-center justify-between">
|
|
453
|
+
<SectionLabel>Environments</SectionLabel>
|
|
454
|
+
<button
|
|
455
|
+
type="button"
|
|
456
|
+
onClick={() => commit([...rows, { slug: "" }])}
|
|
457
|
+
className="text-[10px] font-medium text-[var(--stgm-primary,#6366f1)]"
|
|
458
|
+
data-testid="agent-call-envref-add"
|
|
459
|
+
>
|
|
460
|
+
+ Bind environment
|
|
461
|
+
</button>
|
|
462
|
+
</div>
|
|
463
|
+
{rows.length === 0 ? (
|
|
464
|
+
<p className="text-[10px] leading-tight text-[var(--stgm-muted-foreground,#737373)]">
|
|
465
|
+
Environment resources whose values are provided to this call's runs
|
|
466
|
+
— credentials bind here, not on the agent.
|
|
467
|
+
</p>
|
|
468
|
+
) : (
|
|
469
|
+
<div className="flex flex-col gap-1.5">
|
|
470
|
+
{rows.map((row, i) => (
|
|
471
|
+
<div key={i} className="flex items-start gap-1">
|
|
472
|
+
<input
|
|
473
|
+
type="text"
|
|
474
|
+
value={row.org ?? ""}
|
|
475
|
+
onChange={(e) => updateRow(i, "org", e.target.value)}
|
|
476
|
+
placeholder="org (optional)"
|
|
477
|
+
className={`${inputClass} w-1/3`}
|
|
478
|
+
data-testid={`agent-call-envref-org-${i}`}
|
|
479
|
+
/>
|
|
480
|
+
<input
|
|
481
|
+
type="text"
|
|
482
|
+
value={row.slug ?? ""}
|
|
483
|
+
onChange={(e) => updateRow(i, "slug", e.target.value)}
|
|
484
|
+
placeholder="environment slug"
|
|
485
|
+
className={`${inputClass} flex-1`}
|
|
486
|
+
data-testid={`agent-call-envref-slug-${i}`}
|
|
487
|
+
/>
|
|
488
|
+
<button
|
|
489
|
+
type="button"
|
|
490
|
+
onClick={() => commit(rows.filter((_, j) => j !== i))}
|
|
491
|
+
aria-label={`Remove environment reference ${i + 1}`}
|
|
492
|
+
className="mt-1 text-[var(--stgm-muted-foreground,#737373)] hover:text-[var(--stgm-destructive,#ef4444)]"
|
|
493
|
+
>
|
|
494
|
+
×
|
|
495
|
+
</button>
|
|
496
|
+
</div>
|
|
497
|
+
))}
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
500
|
+
</section>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
281
504
|
// ---------------------------------------------------------------------------
|
|
282
505
|
// Output schema editor (JSON textarea with parse validation)
|
|
283
506
|
// ---------------------------------------------------------------------------
|
|
@@ -20,7 +20,8 @@ const INVOCATION_KINDS = new Set(["http_call", "grpc_call", "activity_call", "ru
|
|
|
20
20
|
* Runtime tab — timeout, retry, budget, and execution policies.
|
|
21
21
|
*
|
|
22
22
|
* Content varies by task kind category:
|
|
23
|
-
* -
|
|
23
|
+
* - agent_call: model + budget per run (the shared run_config block)
|
|
24
|
+
* - llm_call / eval: timeout, max cost
|
|
24
25
|
* - Invocation kinds: timeout
|
|
25
26
|
* - Container kinds: concurrency, join policy
|
|
26
27
|
*
|
|
@@ -43,8 +44,12 @@ export const RuntimeTab = memo(function RuntimeTab({
|
|
|
43
44
|
|
|
44
45
|
return (
|
|
45
46
|
<div className="flex flex-col gap-4 px-3 py-3">
|
|
46
|
-
{
|
|
47
|
-
<
|
|
47
|
+
{kindString === "agent_call" && (
|
|
48
|
+
<AgentCallRuntimeSection config={config} onChange={handleChange} />
|
|
49
|
+
)}
|
|
50
|
+
|
|
51
|
+
{isAi && kindString !== "agent_call" && (
|
|
52
|
+
<AgentRuntimeSection config={config} onChange={handleChange} />
|
|
48
53
|
)}
|
|
49
54
|
|
|
50
55
|
{isInvocation && (
|
|
@@ -69,16 +74,77 @@ export const RuntimeTab = memo(function RuntimeTab({
|
|
|
69
74
|
});
|
|
70
75
|
|
|
71
76
|
// ---------------------------------------------------------------------------
|
|
72
|
-
// Agent
|
|
77
|
+
// Agent call runtime (shared run_config vocabulary)
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Edits the agent_call `run_config` block (the shared agentexecution
|
|
82
|
+
* RunConfig, stigmer/stigmer#358): model override + per-run USD budget,
|
|
83
|
+
* both enforced by the runner. `max_tool_rounds` stays off the form
|
|
84
|
+
* (DD-018 D-5). Empty means omit — a blank field never becomes a zero
|
|
85
|
+
* override.
|
|
86
|
+
*/
|
|
87
|
+
function AgentCallRuntimeSection({
|
|
88
|
+
config,
|
|
89
|
+
onChange,
|
|
90
|
+
}: {
|
|
91
|
+
config: Record<string, unknown>;
|
|
92
|
+
onChange: (field: string, value: unknown) => void;
|
|
93
|
+
}) {
|
|
94
|
+
const runConfig = (config.run_config ?? {}) as Record<string, unknown>;
|
|
95
|
+
|
|
96
|
+
const handleRunConfigChange = useCallback(
|
|
97
|
+
(field: string, value: unknown) => {
|
|
98
|
+
const next: Record<string, unknown> = {
|
|
99
|
+
...runConfig,
|
|
100
|
+
[field]: value === "" ? undefined : value,
|
|
101
|
+
};
|
|
102
|
+
for (const key of Object.keys(next)) {
|
|
103
|
+
if (next[key] === undefined) delete next[key];
|
|
104
|
+
}
|
|
105
|
+
onChange("run_config", Object.keys(next).length > 0 ? next : undefined);
|
|
106
|
+
},
|
|
107
|
+
[runConfig, onChange],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<section className="flex flex-col gap-3">
|
|
112
|
+
<SectionLabel>Execution</SectionLabel>
|
|
113
|
+
|
|
114
|
+
<FieldRow label="Model" hint="Override agent default model">
|
|
115
|
+
<input
|
|
116
|
+
type="text"
|
|
117
|
+
value={typeof runConfig.model_name === "string" ? runConfig.model_name : ""}
|
|
118
|
+
onChange={(e) => handleRunConfigChange("model_name", e.target.value || undefined)}
|
|
119
|
+
placeholder="Agent default"
|
|
120
|
+
className={inputClass}
|
|
121
|
+
/>
|
|
122
|
+
</FieldRow>
|
|
123
|
+
|
|
124
|
+
<FieldRow label="Budget per run (USD)" hint="The call stops when its estimated cost reaches this amount">
|
|
125
|
+
<input
|
|
126
|
+
type="number"
|
|
127
|
+
value={typeof runConfig.max_cost_usd === "number" ? runConfig.max_cost_usd : ""}
|
|
128
|
+
onChange={(e) => handleRunConfigChange("max_cost_usd", e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
129
|
+
placeholder="No limit"
|
|
130
|
+
min={0}
|
|
131
|
+
step={0.05}
|
|
132
|
+
className={inputClass}
|
|
133
|
+
/>
|
|
134
|
+
</FieldRow>
|
|
135
|
+
</section>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// LLM/eval runtime
|
|
73
141
|
// ---------------------------------------------------------------------------
|
|
74
142
|
|
|
75
143
|
function AgentRuntimeSection({
|
|
76
144
|
config,
|
|
77
|
-
kindString,
|
|
78
145
|
onChange,
|
|
79
146
|
}: {
|
|
80
147
|
config: Record<string, unknown>;
|
|
81
|
-
kindString: string;
|
|
82
148
|
onChange: (field: string, value: unknown) => void;
|
|
83
149
|
}) {
|
|
84
150
|
const nestedConfig = (config.config ?? {}) as Record<string, unknown>;
|
|
@@ -94,18 +160,6 @@ function AgentRuntimeSection({
|
|
|
94
160
|
<section className="flex flex-col gap-3">
|
|
95
161
|
<SectionLabel>Execution</SectionLabel>
|
|
96
162
|
|
|
97
|
-
{kindString === "agent_call" && (
|
|
98
|
-
<FieldRow label="Model" hint="Override agent default model">
|
|
99
|
-
<input
|
|
100
|
-
type="text"
|
|
101
|
-
value={typeof nestedConfig.model === "string" ? nestedConfig.model : ""}
|
|
102
|
-
onChange={(e) => handleNestedChange("model", e.target.value || undefined)}
|
|
103
|
-
placeholder="Agent default"
|
|
104
|
-
className={inputClass}
|
|
105
|
-
/>
|
|
106
|
-
</FieldRow>
|
|
107
|
-
)}
|
|
108
|
-
|
|
109
163
|
<FieldRow label="Timeout" hint="Seconds before the task is cancelled">
|
|
110
164
|
<input
|
|
111
165
|
type="number"
|
|
@@ -118,21 +172,6 @@ function AgentRuntimeSection({
|
|
|
118
172
|
/>
|
|
119
173
|
</FieldRow>
|
|
120
174
|
|
|
121
|
-
{kindString === "agent_call" && (
|
|
122
|
-
<FieldRow label="Temperature" hint="0.0 (deterministic) to 1.0 (creative)">
|
|
123
|
-
<input
|
|
124
|
-
type="number"
|
|
125
|
-
value={typeof nestedConfig.temperature === "number" ? nestedConfig.temperature : ""}
|
|
126
|
-
onChange={(e) => handleNestedChange("temperature", e.target.value ? parseFloat(e.target.value) : undefined)}
|
|
127
|
-
placeholder="0.7"
|
|
128
|
-
min={0}
|
|
129
|
-
max={1}
|
|
130
|
-
step={0.1}
|
|
131
|
-
className={inputClass}
|
|
132
|
-
/>
|
|
133
|
-
</FieldRow>
|
|
134
|
-
)}
|
|
135
|
-
|
|
136
175
|
<FieldRow label="Max cost" hint="Per-task cost cap in micro-USD">
|
|
137
176
|
<input
|
|
138
177
|
type="number"
|