* fix: make cleanup "keep" persist subagent sessions indefinitely * feat: expose subagent session metadata in sessions list * fix: include status and timing in sessions_list tool * fix: hide injected timestamp prefixes in chat ui * feat: push session list updates over websocket * feat: expose child subagent sessions in subagents list * feat: add admin http endpoint to kill sessions * Emit session.message websocket events for transcript updates * Estimate session costs in sessions list * Add direct session history HTTP and SSE endpoints * Harden dashboard session events and history APIs * Add session lifecycle gateway methods * Add dashboard session API improvements * Add dashboard session model and parent linkage support * fix: tighten dashboard session API metadata * Fix dashboard session cost metadata * Persist accumulated session cost * fix: stop followup queue drain cfg crash * Fix dashboard session create and model metadata * fix: stop guessing session model costs * Gateway: cache OpenRouter pricing for configured models * Gateway: add timeout session status * Fix subagent spawn test config loading * Gateway: preserve operator scopes without device identity * Emit user message transcript events and deduplicate plugin warnings * feat: emit sessions.changed lifecycle event on subagent spawn Adds a session-lifecycle-events module (similar to transcript-events) that emits create events when subagents are spawned. The gateway server.impl.ts listens for these events and broadcasts sessions.changed with reason=create to SSE subscribers, so dashboards can pick up new subagent sessions without polling. * Gateway: allow persistent dashboard orchestrator sessions * fix: preserve operator scopes for token-authenticated backend clients Backend clients (like agent-dashboard) that authenticate with a valid gateway token but don't present a device identity were getting their scopes stripped. The scope-clearing logic ran before checking the device identity decision, so even when evaluateMissingDeviceIdentity returned 'allow' (because roleCanSkipDeviceIdentity passed for token-authed operators), scopes were already cleared. Fix: also check decision.kind before clearing scopes, so token-authenticated operators keep their requested scopes. * Gateway: allow operator-token session kills * Fix stale active subagent status after follow-up runs * Fix dashboard image attachments in sessions send * Fix completed session follow-up status updates * feat: stream session tool events to operator UIs * Add sessions.steer gateway coverage * Persist subagent timing in session store * Fix subagent session transcript event keys * Fix active subagent session status in gateway * bump session label max to 512 * Fix gateway send session reactivation * fix: publish terminal session lifecycle state * feat: change default session reset to effectively never - Change DEFAULT_RESET_MODE from "daily" to "idle" - Change DEFAULT_IDLE_MINUTES from 60 to 0 (0 = disabled/never) - Allow idleMinutes=0 through normalization (don't clamp to 1) - Treat idleMinutes=0 as "no idle expiry" in evaluateSessionFreshness - Default behavior: mode "idle" + idleMinutes 0 = sessions never auto-reset - Update test assertion for new default mode * fix: prep session management followups (#50101) (thanks @clay-datacurve) --------- Co-authored-by: Tyler Yust <TYTYYUST@YAHOO.COM>
200 lines
7.3 KiB
TypeScript
200 lines
7.3 KiB
TypeScript
import { z } from "zod";
|
|
import { isValidNonNegativeByteSizeString } from "./byte-size.js";
|
|
import {
|
|
HeartbeatSchema,
|
|
AgentSandboxSchema,
|
|
AgentModelSchema,
|
|
MemorySearchSchema,
|
|
} from "./zod-schema.agent-runtime.js";
|
|
import {
|
|
BlockStreamingChunkSchema,
|
|
BlockStreamingCoalesceSchema,
|
|
CliBackendSchema,
|
|
HumanDelaySchema,
|
|
TypingModeSchema,
|
|
} from "./zod-schema.core.js";
|
|
|
|
export const AgentDefaultsSchema = z
|
|
.object({
|
|
model: AgentModelSchema.optional(),
|
|
imageModel: AgentModelSchema.optional(),
|
|
imageGenerationModel: AgentModelSchema.optional(),
|
|
pdfModel: AgentModelSchema.optional(),
|
|
pdfMaxBytesMb: z.number().positive().optional(),
|
|
pdfMaxPages: z.number().int().positive().optional(),
|
|
models: z
|
|
.record(
|
|
z.string(),
|
|
z
|
|
.object({
|
|
alias: z.string().optional(),
|
|
/** Provider-specific API parameters (e.g., GLM-4.7 thinking mode). */
|
|
params: z.record(z.string(), z.unknown()).optional(),
|
|
/** Enable streaming for this model (default: true, false for Ollama to avoid SDK issue #1205). */
|
|
streaming: z.boolean().optional(),
|
|
})
|
|
.strict(),
|
|
)
|
|
.optional(),
|
|
workspace: z.string().optional(),
|
|
repoRoot: z.string().optional(),
|
|
skipBootstrap: z.boolean().optional(),
|
|
bootstrapMaxChars: z.number().int().positive().optional(),
|
|
bootstrapTotalMaxChars: z.number().int().positive().optional(),
|
|
bootstrapPromptTruncationWarning: z
|
|
.union([z.literal("off"), z.literal("once"), z.literal("always")])
|
|
.optional(),
|
|
userTimezone: z.string().optional(),
|
|
timeFormat: z.union([z.literal("auto"), z.literal("12"), z.literal("24")]).optional(),
|
|
envelopeTimezone: z.string().optional(),
|
|
envelopeTimestamp: z.union([z.literal("on"), z.literal("off")]).optional(),
|
|
envelopeElapsed: z.union([z.literal("on"), z.literal("off")]).optional(),
|
|
contextTokens: z.number().int().positive().optional(),
|
|
cliBackends: z.record(z.string(), CliBackendSchema).optional(),
|
|
memorySearch: MemorySearchSchema,
|
|
contextPruning: z
|
|
.object({
|
|
mode: z.union([z.literal("off"), z.literal("cache-ttl")]).optional(),
|
|
ttl: z.string().optional(),
|
|
keepLastAssistants: z.number().int().nonnegative().optional(),
|
|
softTrimRatio: z.number().min(0).max(1).optional(),
|
|
hardClearRatio: z.number().min(0).max(1).optional(),
|
|
minPrunableToolChars: z.number().int().nonnegative().optional(),
|
|
tools: z
|
|
.object({
|
|
allow: z.array(z.string()).optional(),
|
|
deny: z.array(z.string()).optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
softTrim: z
|
|
.object({
|
|
maxChars: z.number().int().nonnegative().optional(),
|
|
headChars: z.number().int().nonnegative().optional(),
|
|
tailChars: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
hardClear: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
placeholder: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
compaction: z
|
|
.object({
|
|
mode: z.union([z.literal("default"), z.literal("safeguard")]).optional(),
|
|
reserveTokens: z.number().int().nonnegative().optional(),
|
|
keepRecentTokens: z.number().int().positive().optional(),
|
|
reserveTokensFloor: z.number().int().nonnegative().optional(),
|
|
maxHistoryShare: z.number().min(0.1).max(0.9).optional(),
|
|
customInstructions: z.string().optional(),
|
|
identifierPolicy: z
|
|
.union([z.literal("strict"), z.literal("off"), z.literal("custom")])
|
|
.optional(),
|
|
identifierInstructions: z.string().optional(),
|
|
recentTurnsPreserve: z.number().int().min(0).max(12).optional(),
|
|
qualityGuard: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
maxRetries: z.number().int().nonnegative().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
postIndexSync: z.enum(["off", "async", "await"]).optional(),
|
|
postCompactionSections: z.array(z.string()).optional(),
|
|
model: z.string().optional(),
|
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
memoryFlush: z
|
|
.object({
|
|
enabled: z.boolean().optional(),
|
|
softThresholdTokens: z.number().int().nonnegative().optional(),
|
|
forceFlushTranscriptBytes: z
|
|
.union([
|
|
z.number().int().nonnegative(),
|
|
z
|
|
.string()
|
|
.refine(isValidNonNegativeByteSizeString, "Expected byte size string like 2mb"),
|
|
])
|
|
.optional(),
|
|
prompt: z.string().optional(),
|
|
systemPrompt: z.string().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
embeddedPi: z
|
|
.object({
|
|
projectSettingsPolicy: z
|
|
.union([z.literal("trusted"), z.literal("sanitize"), z.literal("ignore")])
|
|
.optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
thinkingDefault: z
|
|
.union([
|
|
z.literal("off"),
|
|
z.literal("minimal"),
|
|
z.literal("low"),
|
|
z.literal("medium"),
|
|
z.literal("high"),
|
|
z.literal("xhigh"),
|
|
z.literal("adaptive"),
|
|
])
|
|
.optional(),
|
|
verboseDefault: z.union([z.literal("off"), z.literal("on"), z.literal("full")]).optional(),
|
|
elevatedDefault: z
|
|
.union([z.literal("off"), z.literal("on"), z.literal("ask"), z.literal("full")])
|
|
.optional(),
|
|
blockStreamingDefault: z.union([z.literal("off"), z.literal("on")]).optional(),
|
|
blockStreamingBreak: z.union([z.literal("text_end"), z.literal("message_end")]).optional(),
|
|
blockStreamingChunk: BlockStreamingChunkSchema.optional(),
|
|
blockStreamingCoalesce: BlockStreamingCoalesceSchema.optional(),
|
|
humanDelay: HumanDelaySchema.optional(),
|
|
timeoutSeconds: z.number().int().positive().optional(),
|
|
mediaMaxMb: z.number().positive().optional(),
|
|
imageMaxDimensionPx: z.number().int().positive().optional(),
|
|
typingIntervalSeconds: z.number().int().positive().optional(),
|
|
typingMode: TypingModeSchema.optional(),
|
|
heartbeat: HeartbeatSchema,
|
|
maxConcurrent: z.number().int().positive().optional(),
|
|
subagents: z
|
|
.object({
|
|
maxConcurrent: z.number().int().positive().optional(),
|
|
maxSpawnDepth: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(5)
|
|
.optional()
|
|
.describe(
|
|
"Maximum nesting depth for sub-agent spawning. 1 = no nesting (default), 2 = sub-agents can spawn sub-sub-agents.",
|
|
),
|
|
maxChildrenPerAgent: z
|
|
.number()
|
|
.int()
|
|
.min(1)
|
|
.max(20)
|
|
.optional()
|
|
.describe(
|
|
"Maximum number of active children a single agent session can spawn (default: 5).",
|
|
),
|
|
archiveAfterMinutes: z.number().int().min(0).optional(),
|
|
model: AgentModelSchema.optional(),
|
|
thinking: z.string().optional(),
|
|
runTimeoutSeconds: z.number().int().min(0).optional(),
|
|
announceTimeoutMs: z.number().int().positive().optional(),
|
|
})
|
|
.strict()
|
|
.optional(),
|
|
sandbox: AgentSandboxSchema,
|
|
})
|
|
.strict()
|
|
.optional();
|