Merge remote-tracking branch 'origin/master' into dshw/pr-2250
This commit is contained in:
422 files changed
+15607
-1120
No files matched your search
@@ -107,8 +107,19 @@ import { canOpenNativePath, openNativePath, openNativeTextFile } from './native-
|
||||
/** Page size when history is called without maxMessages. */
|
||||
const DEFAULT_MAX_MESSAGES = 50
|
||||
|
||||
/** Non-model settings namespaces intentionally served to the Web client. */
|
||||
const WEB_SETTINGS_NAMESPACES = ['locale', 'permission', 'ui-conversation', 'ui-theme'] as const
|
||||
/**
|
||||
* Non-model settings namespaces intentionally served to the Web client. The
|
||||
* plugin-owned entries (`agent-loop`, `bash`, `web-search-deepseek`) are the
|
||||
* host-plane sections the plugin configuration page edits; a namespace absent
|
||||
* here answers `settings-not-exposed` even when its owner registered it, so
|
||||
* adding a section to that page is a decision made here rather than by the
|
||||
* registering plugin. Moving that declaration to `settings.register()`, so a
|
||||
* plugin can expose its own configuration without a change in this package,
|
||||
* is deferred work.
|
||||
*/
|
||||
const WEB_SETTINGS_NAMESPACES = [
|
||||
'agent-loop', 'bash', 'locale', 'permission', 'ui-conversation', 'ui-theme', 'web-search-deepseek',
|
||||
] as const
|
||||
|
||||
/** Provider work budget: at most 100 calls and 2,000 inspected hits. */
|
||||
const SESSION_SEARCH_PROVIDER_CALL_LIMIT = 100
|
||||
@@ -236,6 +247,25 @@ function referencedImage(events: readonly SessionEvent[], attachmentId: string):
|
||||
*/
|
||||
const PRODUCT_SETTINGS_NAMESPACES = new Set(['ui-onboarding', AGENT_PRESET_SETTINGS_NAMESPACE])
|
||||
|
||||
/** Strict browser-zone profile: UTC or an IANA Area/Location-style identifier. */
|
||||
const IANA_TIME_ZONE = /^[A-Za-z][A-Za-z0-9_+.-]*(?:\/[A-Za-z0-9_+.-]+)+$/
|
||||
|
||||
/** Validate and canonicalize one browser-supplied IANA zone at the wire boundary. */
|
||||
function canonicalClientTimeZone(value: string): string | undefined {
|
||||
if (value.length === 0 || value.trim() !== value
|
||||
|| (value !== 'UTC' && !IANA_TIME_ZONE.test(value))) return undefined
|
||||
try {
|
||||
const canonical = new Intl.DateTimeFormat('en-US', { timeZone: value })
|
||||
.resolvedOptions().timeZone
|
||||
/* v8 ignore next -- Intl returns UTC or a canonical IANA Area/Location for accepted input. */
|
||||
if (canonical !== 'UTC' && !IANA_TIME_ZONE.test(canonical)) return undefined
|
||||
return canonical
|
||||
} catch {
|
||||
// Intl rejects unsupported zone names; the RPC maps that parser rejection below.
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/** Read live abort state across awaits without treating it as synchronously immutable. */
|
||||
function isAborted(signal: AbortSignal): boolean {
|
||||
return signal.aborted
|
||||
@@ -2322,12 +2352,26 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
},
|
||||
|
||||
async prompt(request) {
|
||||
const { sessionId, mode, content } = request.payload
|
||||
const { sessionId, mode, content, clientTimeZone } = request.payload
|
||||
const canonicalTimeZone = clientTimeZone === undefined
|
||||
? undefined
|
||||
: canonicalClientTimeZone(clientTimeZone)
|
||||
if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
|
||||
return err(request, {
|
||||
code: 'invalid-time-zone',
|
||||
message: 'clientTimeZone must be UTC or a valid IANA Area/Location name',
|
||||
details: { value: clientTimeZone },
|
||||
})
|
||||
}
|
||||
const resolved = await turnAgentFor<{ accepted: true }>(request, sessionId)
|
||||
if ('refused' in resolved) return resolved.refused
|
||||
const agent = resolved.agent
|
||||
// The rpcId rides MessageSource into user/message (merge declaration in api/sessions.ts; provisional correlation).
|
||||
const source: MessageSource = { kind: 'user', rpcId: request.rpcId }
|
||||
// Request identity and optional browser zone ride the exact durable user message.
|
||||
const source: MessageSource = {
|
||||
kind: 'user',
|
||||
rpcId: request.rpcId,
|
||||
...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
|
||||
}
|
||||
const hasImage = content.some(part => part.type === 'image')
|
||||
const admit = async (): Promise<RpcResponse<{ accepted: true }>> => {
|
||||
try {
|
||||
@@ -2584,7 +2628,17 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
},
|
||||
|
||||
async prompt(request, signal) {
|
||||
const { parentSessionId, childSessionId, content } = request.payload
|
||||
const { parentSessionId, childSessionId, content, clientTimeZone } = request.payload
|
||||
const canonicalTimeZone = clientTimeZone === undefined
|
||||
? undefined
|
||||
: canonicalClientTimeZone(clientTimeZone)
|
||||
if (clientTimeZone !== undefined && canonicalTimeZone === undefined) {
|
||||
return err(request, {
|
||||
code: 'invalid-time-zone',
|
||||
message: 'clientTimeZone must be UTC or a valid IANA Area/Location name',
|
||||
details: { value: clientTimeZone },
|
||||
})
|
||||
}
|
||||
const parent = ctx.agents.get(parentSessionId)
|
||||
if (parent === undefined) {
|
||||
return err(request, {
|
||||
@@ -2599,7 +2653,11 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
if (verified.error !== undefined) return err(request, verified.error)
|
||||
try {
|
||||
const messageId = await ctx.subagents.followup(parent, childSessionId, content, {
|
||||
source: { kind: 'user', rpcId: request.rpcId },
|
||||
source: {
|
||||
kind: 'user',
|
||||
rpcId: request.rpcId,
|
||||
...(canonicalTimeZone === undefined ? {} : { clientTimeZone: canonicalTimeZone }),
|
||||
},
|
||||
signal,
|
||||
})
|
||||
return ok(request, { messageId })
|
||||
@@ -2769,6 +2827,7 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
|
||||
provider: selection.provider,
|
||||
model: selection.model,
|
||||
attachedSessions: ctx.agents.list().length,
|
||||
canOpenPath: canOpenPaths(),
|
||||
}))
|
||||
},
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ export const hostDescribeValueSchema = z.object({
|
||||
provider: z.string().optional(),
|
||||
model: z.string().optional(),
|
||||
attachedSessions: z.number().int().nonnegative(),
|
||||
canOpenPath: z.boolean(),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'host.describe'>>>
|
||||
|
||||
/** host.pickDirectory request payload (empty object literal). */
|
||||
|
||||
@@ -41,6 +41,7 @@ export interface HostApi {
|
||||
* applied when a new agent doesn't specify them explicitly, absent when the host configures
|
||||
* no explicit default (the adapter falls back internally);
|
||||
* attachedSessions = count of currently attached sessions (those with a live agent);
|
||||
* canOpenPath = whether this deployment can hand a path to a user-visible native desktop.
|
||||
*/
|
||||
describe(request: RpcRequest<{}>): Promise<RpcResponse<{
|
||||
version: string
|
||||
@@ -48,6 +49,7 @@ export interface HostApi {
|
||||
provider?: string
|
||||
model?: string
|
||||
attachedSessions: number
|
||||
canOpenPath: boolean
|
||||
}>>
|
||||
|
||||
/**
|
||||
|
||||
@@ -37,6 +37,7 @@ export const rpcErrorSchema: z.ZodType<RpcError> = z.discriminatedUnion('code',
|
||||
z.object({ code: z.literal('session-not-found'), message: z.string(), details: z.object({ sessionId: z.string() }) }),
|
||||
z.object({ code: z.literal('model-unavailable'), message: z.string(), details: z.object({ provider: z.string(), model: z.string() }) }),
|
||||
z.object({ code: z.literal('session-conflict'), message: z.string(), details: z.object({ sessionId: z.string(), requestedCwd: z.string(), existingCwd: z.string().optional() }) }),
|
||||
z.object({ code: z.literal('invalid-time-zone'), message: z.string(), details: z.object({ value: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-attach-failed'), message: z.string(), details: z.object({ sessionId: z.string(), workspaceId: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-not-found'), message: z.string(), details: z.object({ workspaceId: z.string() }) }),
|
||||
z.object({ code: z.literal('workspace-invalid-path'), message: z.string(), details: z.object({ path: z.string() }) }),
|
||||
|
||||
@@ -35,6 +35,7 @@ export interface RpcErrorDetailsMap {
|
||||
'session-not-found': { sessionId: SessionId }
|
||||
'model-unavailable': { provider: string; model: string }
|
||||
'session-conflict': { sessionId: SessionId; requestedCwd: string; existingCwd?: string }
|
||||
'invalid-time-zone': { value: string }
|
||||
'workspace-attach-failed': { sessionId: SessionId; workspaceId: string }
|
||||
'workspace-not-found': { workspaceId: string }
|
||||
'workspace-invalid-path': { path: string }
|
||||
|
||||
@@ -265,11 +265,12 @@ export const promptContentPartSchema = z.discriminatedUnion('type', [
|
||||
z.object({ type: z.literal('image'), mediaType: imageMediaTypeSchema, data: z.string(), name: z.string().optional() }),
|
||||
])
|
||||
|
||||
/** session.prompt request payload. */
|
||||
/** session.prompt request payload, including optional browser-local request provenance. */
|
||||
export const sessionPromptRequestSchema = z.object({
|
||||
sessionId: sessionIdSchema,
|
||||
mode: z.union([z.literal('queue'), z.literal('steer')]),
|
||||
content: z.array(promptContentPartSchema),
|
||||
clientTimeZone: z.string().optional(),
|
||||
}) as unknown as z.ZodType<RequestPayload<'session.prompt'>>
|
||||
|
||||
/** session.prompt response value (the command slot appears only when the prompt dispatched a slash command). */
|
||||
|
||||
@@ -21,9 +21,10 @@ declare module '@deepseek-ai/dsh-llm' {
|
||||
* The prompt's rpcId is passed through MessageSource into the `user/message` event
|
||||
* (the client uses it to reconcile the optimistically
|
||||
* echoed provisional message with the event stream). kind stays `'user'` — the model face
|
||||
* carries no transport vocabulary; rpcId is an extra durable-JSON field passed back to the client with the event.
|
||||
* carries no transport vocabulary; rpcId and the optional Host-validated browser zone are
|
||||
* durable JSON fields passed back to the client with the event.
|
||||
*/
|
||||
'user-rpc': { kind: 'user'; rpcId: RpcId }
|
||||
'user-rpc': { kind: 'user'; rpcId: RpcId; clientTimeZone?: string }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,8 +309,19 @@ export interface SessionsApi {
|
||||
fork(request: RpcRequest<{ sessionId: SessionId; atSeq?: number }>):
|
||||
Promise<RpcResponse<{ sessionId: SessionId }>>
|
||||
|
||||
/** Sends text and temporary image bytes after durable host admission. Session-backed subagents reject with `agent-busy`. */
|
||||
prompt(request: RpcRequest<{ sessionId: SessionId; mode: 'queue' | 'steer'; content: PromptContentPart[] }>):
|
||||
/**
|
||||
* Sends text and temporary image bytes to an ordinary session Agent after durable host admission.
|
||||
* Browser callers attach their current IANA zone;
|
||||
* the Host validates, canonicalizes, and records it on that exact user message. Omission remains
|
||||
* valid for non-browser callers. Session-backed subagents reject with `agent-busy` and use
|
||||
* `subagent.prompt`.
|
||||
*/
|
||||
prompt(request: RpcRequest<{
|
||||
sessionId: SessionId
|
||||
mode: 'queue' | 'steer'
|
||||
content: PromptContentPart[]
|
||||
clientTimeZone?: string
|
||||
}>):
|
||||
Promise<RpcResponse<{ accepted: true; command?: { kind: 'success'; text?: string } }>>
|
||||
|
||||
/** Reads one durable image after proving that this session's log references its id. */
|
||||
|
||||
@@ -67,6 +67,7 @@ export const subagentPromptRequestSchema = z.object({
|
||||
childSessionId: sessionIdSchema,
|
||||
mode: z.literal('continuable'),
|
||||
content: z.array(contentBlockSchema),
|
||||
clientTimeZone: z.string().optional(),
|
||||
}) as unknown as z.ZodType<RequestPayload<'subagent.prompt'>>
|
||||
|
||||
/** subagent.interrupt request payload. */
|
||||
|
||||
@@ -92,10 +92,15 @@ export interface SubagentsApi {
|
||||
* Delivers human content to a continuable child through the exact live
|
||||
* parent's continuation owner. Success identifies the message accepted by
|
||||
* the child's FIFO inbox; later execution is independent of this request.
|
||||
* Optional browser-zone provenance is validated and logged on that message.
|
||||
*/
|
||||
prompt(
|
||||
request: RpcRequest<
|
||||
Extract<SubagentAddress, { mode: 'continuable' }> & { content: ContentBlock[] }
|
||||
Extract<SubagentAddress, { mode: 'continuable' }> & {
|
||||
content: ContentBlock[]
|
||||
/** Optional browser zone sampled for this exact human prompt. */
|
||||
clientTimeZone?: string
|
||||
}
|
||||
>,
|
||||
signal: AbortSignal,
|
||||
): Promise<RpcResponse<SubagentPromptReceipt>>
|
||||
|
||||
Reference in New Issue
Block a user