stagent 0.3.5 → 0.3.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stagent",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Governed AI agent workspace for supervised local execution, workflows, documents, and provider runtimes.",
5
5
  "keywords": [
6
6
  "ai",
@@ -0,0 +1,25 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ import { getSetting, setSetting } from "@/lib/settings/helpers";
3
+ import { SETTINGS_KEYS } from "@/lib/constants/settings";
4
+
5
+ export async function GET() {
6
+ const sdkTimeoutSeconds = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
7
+ return NextResponse.json({
8
+ sdkTimeoutSeconds: sdkTimeoutSeconds ?? "60",
9
+ });
10
+ }
11
+
12
+ export async function POST(req: NextRequest) {
13
+ const body = await req.json();
14
+ const seconds = parseInt(body.sdkTimeoutSeconds, 10);
15
+
16
+ if (isNaN(seconds) || seconds < 10 || seconds > 300) {
17
+ return NextResponse.json(
18
+ { error: "sdkTimeoutSeconds must be between 10 and 300" },
19
+ { status: 400 }
20
+ );
21
+ }
22
+
23
+ await setSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS, String(seconds));
24
+ return NextResponse.json({ sdkTimeoutSeconds: String(seconds) });
25
+ }
@@ -4,6 +4,7 @@ import { PermissionsSections } from "@/components/settings/permissions-sections"
4
4
  import { DataManagementSection } from "@/components/settings/data-management-section";
5
5
  import { BudgetGuardrailsSection } from "@/components/settings/budget-guardrails-section";
6
6
  import { ChatSettingsSection } from "@/components/settings/chat-settings-section";
7
+ import { RuntimeTimeoutSection } from "@/components/settings/runtime-timeout-section";
7
8
  import { PageShell } from "@/components/shared/page-shell";
8
9
 
9
10
  export const dynamic = "force-dynamic";
@@ -15,6 +16,7 @@ export default function SettingsPage() {
15
16
  <AuthConfigSection />
16
17
  <OpenAIRuntimeSection />
17
18
  <ChatSettingsSection />
19
+ <RuntimeTimeoutSection />
18
20
  <BudgetGuardrailsSection />
19
21
  <PermissionsSections />
20
22
  <DataManagementSection />
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState, useCallback } from "react";
4
+ import { Timer } from "lucide-react";
5
+ import { toast } from "sonner";
6
+ import {
7
+ Card,
8
+ CardContent,
9
+ CardDescription,
10
+ CardHeader,
11
+ CardTitle,
12
+ } from "@/components/ui/card";
13
+ import { Input } from "@/components/ui/input";
14
+ import { FormSectionCard } from "@/components/shared/form-section-card";
15
+
16
+ const SETTING_KEY = "runtime.sdkTimeoutSeconds";
17
+ const DEFAULT_TIMEOUT = 60;
18
+
19
+ export function RuntimeTimeoutSection() {
20
+ const [timeout, setTimeout_] = useState(String(DEFAULT_TIMEOUT));
21
+ const [saving, setSaving] = useState(false);
22
+
23
+ const fetchTimeout = useCallback(async () => {
24
+ try {
25
+ const res = await fetch("/api/settings/runtime");
26
+ if (res.ok) {
27
+ const data = await res.json();
28
+ if (data.sdkTimeoutSeconds) setTimeout_(data.sdkTimeoutSeconds);
29
+ }
30
+ } catch {
31
+ // Use default
32
+ }
33
+ }, []);
34
+
35
+ useEffect(() => {
36
+ fetchTimeout();
37
+ }, [fetchTimeout]);
38
+
39
+ const handleSave = async (value: string) => {
40
+ const num = parseInt(value, 10);
41
+ if (isNaN(num) || num < 10 || num > 300) {
42
+ toast.error("Timeout must be between 10 and 300 seconds");
43
+ return;
44
+ }
45
+
46
+ setSaving(true);
47
+ try {
48
+ await fetch("/api/settings/runtime", {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify({ sdkTimeoutSeconds: String(num) }),
52
+ });
53
+ setTimeout_(String(num));
54
+ toast.success("Timeout updated");
55
+ } catch {
56
+ toast.error("Failed to save timeout");
57
+ } finally {
58
+ setSaving(false);
59
+ }
60
+ };
61
+
62
+ return (
63
+ <Card>
64
+ <CardHeader>
65
+ <CardTitle>Runtime</CardTitle>
66
+ <CardDescription>
67
+ Configure runtime behavior for AI operations.
68
+ </CardDescription>
69
+ </CardHeader>
70
+ <CardContent>
71
+ <FormSectionCard
72
+ icon={Timer}
73
+ title="SDK Timeout"
74
+ hint="Maximum seconds to wait for AI responses (task assist, completions). Min 10, max 300."
75
+ >
76
+ <Input
77
+ type="number"
78
+ min={10}
79
+ max={300}
80
+ value={timeout}
81
+ onChange={(e) => setTimeout_(e.target.value)}
82
+ onBlur={(e) => handleSave(e.target.value)}
83
+ disabled={saving}
84
+ className="w-32"
85
+ />
86
+ </FormSectionCard>
87
+ </CardContent>
88
+ </Card>
89
+ );
90
+ }
@@ -10,6 +10,8 @@ import { executeClaudeTask, resumeClaudeTask } from "@/lib/agents/claude-agent";
10
10
  import { getRuntimeCapabilities, getRuntimeCatalogEntry } from "./catalog";
11
11
  import { buildClaudeSdkEnv } from "./claude-sdk";
12
12
  import { getLaunchCwd } from "@/lib/environment/workspace-context";
13
+ import { getSetting } from "@/lib/settings/helpers";
14
+ import { SETTINGS_KEYS } from "@/lib/constants/settings";
13
15
  import type {
14
16
  AgentRuntimeAdapter,
15
17
  RuntimeConnectionResult,
@@ -89,6 +91,21 @@ async function collectResultText(
89
91
  return { resultText, usage };
90
92
  }
91
93
 
94
+ /** Read the user-configurable SDK timeout (in ms). Falls back to 60s. */
95
+ async function getSdkTimeout(): Promise<number> {
96
+ const raw = await getSetting(SETTINGS_KEYS.SDK_TIMEOUT_SECONDS);
97
+ const seconds = raw ? parseInt(raw, 10) : 60;
98
+ return (isNaN(seconds) || seconds < 10 ? 60 : seconds) * 1000;
99
+ }
100
+
101
+ /** Check if an error is an abort/timeout error from the SDK. */
102
+ function isAbortError(error: unknown): boolean {
103
+ return (
104
+ error instanceof Error &&
105
+ (error.name === "AbortError" || error.message.includes("aborted"))
106
+ );
107
+ }
108
+
92
109
  async function runSingleProfileTest(
93
110
  profileId: string,
94
111
  test: { task: string; expectedKeywords: string[] }
@@ -106,7 +123,8 @@ async function runSingleProfileTest(
106
123
  const prompt = `${payload.instructions}\n\n---\n\nTask: ${test.task}\n\nProvide a brief analysis (2-3 paragraphs max). Include specific terminology relevant to your domain.`;
107
124
  const authEnv = await getAuthEnv();
108
125
  const abortController = new AbortController();
109
- const timeout = setTimeout(() => abortController.abort(), 30_000);
126
+ const sdkTimeoutMs = await getSdkTimeout();
127
+ const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
110
128
  const startedAt = new Date();
111
129
  let usage: UsageSnapshot = {};
112
130
  let ledgerRecorded = false;
@@ -265,7 +283,8 @@ export async function runMetaCompletion(input: {
265
283
  const startedAt = new Date();
266
284
  let usage: UsageSnapshot = {};
267
285
  const abortController = new AbortController();
268
- const timeout = setTimeout(() => abortController.abort(), 60_000);
286
+ const sdkTimeoutMs = await getSdkTimeout();
287
+ const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
269
288
 
270
289
  try {
271
290
  const response = query({
@@ -312,6 +331,9 @@ export async function runMetaCompletion(input: {
312
331
  startedAt,
313
332
  finishedAt: new Date(),
314
333
  });
334
+ if (isAbortError(error)) {
335
+ throw new Error("Request timed out. You can increase the timeout in Settings → Runtime.");
336
+ }
315
337
  throw error;
316
338
  } finally {
317
339
  clearTimeout(timeout);
@@ -336,7 +358,8 @@ async function runClaudeTaskAssist(
336
358
  let usage: UsageSnapshot = {};
337
359
 
338
360
  const abortController = new AbortController();
339
- const timeout = setTimeout(() => abortController.abort(), 30_000);
361
+ const sdkTimeoutMs = await getSdkTimeout();
362
+ const timeout = setTimeout(() => abortController.abort(), sdkTimeoutMs);
340
363
 
341
364
  try {
342
365
  const response = query({
@@ -394,6 +417,9 @@ async function runClaudeTaskAssist(
394
417
  startedAt,
395
418
  finishedAt: new Date(),
396
419
  });
420
+ if (isAbortError(error)) {
421
+ throw new Error("Request timed out. You can increase the timeout in Settings → Runtime.");
422
+ }
397
423
  throw error;
398
424
  } finally {
399
425
  clearTimeout(timeout);
@@ -28,6 +28,7 @@ export function settingsTools(_ctx: ToolContext) {
28
28
  const keys = [
29
29
  "auth_method",
30
30
  "default_runtime",
31
+ "runtime.sdkTimeoutSeconds",
31
32
  "budget_max_tokens_per_task",
32
33
  "budget_max_cost_per_task",
33
34
  "budget_max_daily_cost",
@@ -8,6 +8,7 @@ export const SETTINGS_KEYS = {
8
8
  BUDGET_POLICY: "usage.budgetPolicy",
9
9
  BUDGET_WARNING_STATE: "usage.budgetWarningState",
10
10
  PRICING_REGISTRY: "usage.pricingRegistry",
11
+ SDK_TIMEOUT_SECONDS: "runtime.sdkTimeoutSeconds",
11
12
  } as const;
12
13
 
13
14
  export type AuthMethod = "api_key" | "oauth";