Merge remote-tracking branch 'origin/master' into fix/windows-native-acl-coverage
This commit is contained in:
@@ -28,7 +28,7 @@ import { bindScopeParent, createScope, scopeOf, type Scope, type ScopeKey, type
|
||||
import { settingsNamespace, type SettingsScope, type default as SettingsService } from '@deepseek-ai/dsh-settings'
|
||||
import { discoverPresets } from './discovery.ts'
|
||||
import { copyComposition, deleteComposition, readComposition } from './authoring.ts'
|
||||
import { mountPreset, serviceForAgent } from './mount.ts'
|
||||
import { mountPreset, serviceForAgent, standingMountFor } from './mount.ts'
|
||||
import { PresetExistsError } from './authoring.ts'
|
||||
import { PresetMountError, UnknownPresetError, type AgentPreset, type Config } from './types.ts'
|
||||
|
||||
@@ -51,8 +51,8 @@ export {
|
||||
METADATA_FILE, readPresetMetadata, renderPresetMetadata, type PresetMetadata,
|
||||
} from './metadata.ts'
|
||||
export {
|
||||
inactiveRows, leakedServices, livePresetMounts, mountPreset, serviceForAgent,
|
||||
type PresetMount,
|
||||
inactiveRows, leakedServices, livePresetMounts, mountPreset, serviceForAgent, standingMountFor,
|
||||
type JoinedPresetMount, type PresetMount,
|
||||
} from './mount.ts'
|
||||
export {
|
||||
copyComposition, deleteComposition, InvalidPresetIdError, PresetExistsError,
|
||||
@@ -238,6 +238,56 @@ export class AgentPresets extends Service {
|
||||
return preset
|
||||
}
|
||||
|
||||
/**
|
||||
* Join one agent to the SAME standing composition another already runs on.
|
||||
*
|
||||
* This is how a child agent inherits its parent's capabilities. It is a bind,
|
||||
* not a mount: the parent's generation is already composed, so the child gets
|
||||
* that exact instance — the same plugin objects, the same tool registrations,
|
||||
* the same prompt sections. Re-resolving the parent's preset by id instead
|
||||
* would re-read the roster, and a composition file edited since the parent
|
||||
* started would hand the child a DIFFERENT generation than the one its
|
||||
* parent's history was produced under (and a preset deleted since would fail
|
||||
* the child outright while its parent keeps running).
|
||||
*
|
||||
* Synchronous, and with no composition failure mode of its own — it reads no
|
||||
* roster, mounts nothing, and touches no file — which is what lets a child
|
||||
* creation window use it: the two in-process subagent drivers compose their
|
||||
* children inside a synchronous `setup`. It still rejects a caller error, as
|
||||
* the `@throws` below record.
|
||||
*
|
||||
* A parent that joined no preset — a rosterless deployment — yields no join
|
||||
* and no error: there, the model-facing rows sit in the host composition and
|
||||
* the child already sees them through the global layer.
|
||||
* @param agentCtx - the joining agent's scope context.
|
||||
* @param parentCtx - the scope context of the agent whose composition to join.
|
||||
* @returns the preset id joined, or undefined when the parent joined none.
|
||||
* @throws when `agentCtx` carries no scope, or has already joined a preset.
|
||||
*/
|
||||
composeFrom(agentCtx: Context, parentCtx: Context): string | undefined {
|
||||
const agentKey = scopeOf(agentCtx)
|
||||
if (agentKey === undefined) {
|
||||
throw new Error('agent-presets: refusing to compose an unscoped context; the scope key is what joins an agent to its preset')
|
||||
}
|
||||
const standing = standingMountFor(parentCtx)
|
||||
if (standing === undefined) return undefined
|
||||
this.bindings.set(agentKey, bindScopeParent(agentKey, standing.key))
|
||||
return standing.presetId
|
||||
}
|
||||
|
||||
/**
|
||||
* The preset one live agent runs on.
|
||||
*
|
||||
* Read from the live scope chain rather than from the session, so it answers
|
||||
* for an agent whose session has not recorded a preset yet — a child agent
|
||||
* whose durable header is being built from its parent's composition.
|
||||
* @param agentCtx - the agent's scope context.
|
||||
* @returns the preset id, or undefined when the agent joined none.
|
||||
*/
|
||||
composedPreset(agentCtx: Context): string | undefined {
|
||||
return standingMountFor(agentCtx)?.presetId
|
||||
}
|
||||
|
||||
/** Whether this deployment configures a root locally authored presets go to. */
|
||||
get authorable(): boolean {
|
||||
return this.config.roots.some(root => root.trust === 'user')
|
||||
|
||||
@@ -202,6 +202,33 @@ export function leakedServices(ctx: Context, mount: Fiber): string[] {
|
||||
return leaked.sort((left, right) => left.localeCompare(right))
|
||||
}
|
||||
|
||||
/** A live standing mount located through one agent already joined to it. */
|
||||
export type JoinedPresetMount = PresetMount & {
|
||||
/** The standing key, definite because it is what the lookup matched on. */
|
||||
readonly key: ScopeKey
|
||||
}
|
||||
|
||||
/**
|
||||
* The standing composition one agent is joined to.
|
||||
*
|
||||
* The agent's own key is parented to its preset's standing key, so the mount
|
||||
* is found by matching that parent rather than by walking up from the agent —
|
||||
* the mount is not under the agent's fiber. An agent that joined no preset —
|
||||
* a deployment composing no roster, or a child agent before its join — has no
|
||||
* parent link and resolves to undefined.
|
||||
* @param agentCtx - the agent's scope context.
|
||||
* @returns the mount the agent joined, or undefined when it joined none.
|
||||
*/
|
||||
export function standingMountFor(agentCtx: Context): JoinedPresetMount | undefined {
|
||||
const agentKey = scopeOf(agentCtx)
|
||||
if (agentKey === undefined) return undefined
|
||||
const standingKey = scopeParentOf(agentKey)
|
||||
if (standingKey === undefined) return undefined
|
||||
return livePresetMounts().find(
|
||||
(candidate): candidate is JoinedPresetMount => candidate.key === standingKey,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* One agent's instance of a service its preset mounted.
|
||||
*
|
||||
@@ -231,14 +258,7 @@ export function serviceForAgent<K extends string & keyof Context>(
|
||||
agent: { ctx: Context },
|
||||
name: K,
|
||||
): Context[K] | undefined {
|
||||
// The agent's own key is parented to its preset's standing key; the mount
|
||||
// is no longer under the agent's fiber, so the search roots at the standing
|
||||
// mount instead of walking up from the agent.
|
||||
const agentKey = scopeOf(agent.ctx)
|
||||
if (agentKey === undefined) return undefined
|
||||
const standingKey = scopeParentOf(agentKey)
|
||||
if (standingKey === undefined) return undefined
|
||||
const mount = livePresetMounts().find(candidate => candidate.key === standingKey)
|
||||
const mount = standingMountFor(agent.ctx)
|
||||
if (mount === undefined) return undefined
|
||||
const store = ctx.reflect.store
|
||||
for (const key of Object.getOwnPropertySymbols(store)) {
|
||||
|
||||
Reference in New Issue
Block a user