pilotswarm-sdk 0.5.38 → 0.5.39
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/api/src/http-api-transport.js +26 -7
- package/api/src/protocol.js +16 -7
- package/dist/agent-manager-tools.d.ts.map +1 -1
- package/dist/agent-manager-tools.js +12 -2
- package/dist/agent-manager-tools.js.map +1 -1
- package/dist/agent-package-installer.d.ts +2 -0
- package/dist/agent-package-installer.d.ts.map +1 -1
- package/dist/agent-package-installer.js +1 -0
- package/dist/agent-package-installer.js.map +1 -1
- package/dist/agent-package-service.d.ts +1 -1
- package/dist/agent-package-service.d.ts.map +1 -1
- package/dist/agent-package-service.js +79 -11
- package/dist/agent-package-service.js.map +1 -1
- package/dist/cms-migrations.d.ts.map +1 -1
- package/dist/cms-migrations.js +59 -0
- package/dist/cms-migrations.js.map +1 -1
- package/dist/cms.d.ts +9 -0
- package/dist/cms.d.ts.map +1 -1
- package/dist/cms.js +10 -0
- package/dist/cms.js.map +1 -1
- package/dist/session-manager.d.ts +84 -7
- package/dist/session-manager.d.ts.map +1 -1
- package/dist/session-manager.js +137 -16
- package/dist/session-manager.js.map +1 -1
- package/dist/session-proxy.d.ts +41 -0
- package/dist/session-proxy.d.ts.map +1 -1
- package/dist/session-proxy.js +188 -159
- package/dist/session-proxy.js.map +1 -1
- package/dist/web/generated-op-methods.d.ts +34 -5
- package/dist/web/generated-op-methods.d.ts.map +1 -1
- package/dist/web/generated-op-methods.js +2 -0
- package/dist/web/generated-op-methods.js.map +1 -1
- package/dist/worker.d.ts +23 -0
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +189 -17
- package/dist/worker.js.map +1 -1
- package/package.json +3 -3
package/dist/session-proxy.js
CHANGED
|
@@ -28,6 +28,140 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
28
28
|
import { attemptStoreRecovery, runTurnCommit, runTurnPreamble } from "./session-lifecycle.js";
|
|
29
29
|
import { supportsVersionedSnapshots, writeTurnSentinel } from "./snapshot-protocol.js";
|
|
30
30
|
const SYSTEM_AGENT_IDS = new Set(["pilotswarm", "sweeper", "resourcemgr", "facts-manager"]);
|
|
31
|
+
/**
|
|
32
|
+
* THE agent-name resolver: FQN parsing, fuzzy matching, package privacy, and
|
|
33
|
+
* owner shadowing in one place.
|
|
34
|
+
*
|
|
35
|
+
* This used to exist twice — the resolveAgentConfig activity had the full
|
|
36
|
+
* rule set while the control bridge's inline copy had none of it, so
|
|
37
|
+
* `spawn_agent`/`create_agent_session` could bind another user's private
|
|
38
|
+
* agent, could not address `__shared:<name>`, and ignored the caller's own
|
|
39
|
+
* shadowing copy. A security rule with two implementations has one that is
|
|
40
|
+
* wrong; this is now the only one.
|
|
41
|
+
*
|
|
42
|
+
* `getCallerOwnerKey` is awaited lazily — only when a user-scope package
|
|
43
|
+
* agent is actually in play — and must resolve to the caller's owner key
|
|
44
|
+
* (`provider\u0001subject`) or null. Fail closed: null means no private
|
|
45
|
+
* agents match.
|
|
46
|
+
*/
|
|
47
|
+
export async function resolveAgentDefinitionForCaller(opts) {
|
|
48
|
+
const agents = [
|
|
49
|
+
...(opts.userAgents ?? []).map(a => ({ ...a, system: false, creatable: true })),
|
|
50
|
+
...(opts.systemAgents ?? []).map(a => ({ ...a, creatable: false })),
|
|
51
|
+
];
|
|
52
|
+
const normalize = (value) => (value || "").toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
53
|
+
// Parse the reference. `a:b` has meant `namespace:agent` since long
|
|
54
|
+
// before FQNs existed, so the parser reports that shape as AMBIGUOUS
|
|
55
|
+
// and we keep the namespace reading first — an existing binding must
|
|
56
|
+
// not change meaning because per-user namespaces shipped.
|
|
57
|
+
const fqn = parseAgentFqn(opts.agentName);
|
|
58
|
+
let lookupNamespace;
|
|
59
|
+
let rawName = opts.agentName;
|
|
60
|
+
/** `__shared:` explicitly asks past the caller's own copy. */
|
|
61
|
+
let sharedOnly = false;
|
|
62
|
+
if (fqn.kind === "shared") {
|
|
63
|
+
rawName = fqn.name;
|
|
64
|
+
sharedOnly = true;
|
|
65
|
+
}
|
|
66
|
+
else if (fqn.kind === "bare") {
|
|
67
|
+
rawName = fqn.name;
|
|
68
|
+
}
|
|
69
|
+
else if (fqn.kind === "ambiguous" || fqn.kind === "owner") {
|
|
70
|
+
rawName = fqn.name;
|
|
71
|
+
lookupNamespace = fqn.namespaceRef ?? fqn.ownerRef;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
// Invalid (reserved prefix, bad semver, malformed). Fall back to
|
|
75
|
+
// the raw string so a legitimately odd agent name still resolves
|
|
76
|
+
// the way it always did; a reserved name simply will not match.
|
|
77
|
+
rawName = opts.agentName;
|
|
78
|
+
}
|
|
79
|
+
const lookup = normalize(rawName);
|
|
80
|
+
// Also try without trailing "agent" suffix for fuzzy matching
|
|
81
|
+
// (LLM often says "Sweeper agent" which normalizes to "sweeperagent", but id is "sweeper")
|
|
82
|
+
const lookupBase = lookup.replace(/agent$/, "");
|
|
83
|
+
let callerKeyResolved = false;
|
|
84
|
+
let callerKey = null;
|
|
85
|
+
const callerOwnerKeyOnce = async () => {
|
|
86
|
+
if (!callerKeyResolved) {
|
|
87
|
+
callerKeyResolved = true;
|
|
88
|
+
try {
|
|
89
|
+
callerKey = await opts.getCallerOwnerKey();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Fail closed: an unresolvable caller gets no private agents.
|
|
93
|
+
callerKey = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return callerKey;
|
|
97
|
+
};
|
|
98
|
+
const ownerKeyOf = (owner) => owner?.provider && owner?.subject ? `${owner.provider}\u0001${owner.subject}` : null;
|
|
99
|
+
const visibleToCaller = async (agent) => {
|
|
100
|
+
const agentOwnerKey = ownerKeyOf(agent?.packageOwner);
|
|
101
|
+
if (agent?.packageScope !== "user" || !agentOwnerKey)
|
|
102
|
+
return true;
|
|
103
|
+
const key = await callerOwnerKeyOnce();
|
|
104
|
+
return key !== null && key === agentOwnerKey;
|
|
105
|
+
};
|
|
106
|
+
const candidatesFor = (a) => [a.name, a.id, a.title].map(normalize).filter(Boolean);
|
|
107
|
+
const nameMatches = (a) => {
|
|
108
|
+
const candidates = candidatesFor(a);
|
|
109
|
+
return candidates.includes(lookup) || (lookupBase && candidates.includes(lookupBase));
|
|
110
|
+
};
|
|
111
|
+
let matches = agents.filter(a => {
|
|
112
|
+
if (lookupNamespace && normalize(a.namespace) !== normalize(lookupNamespace))
|
|
113
|
+
return false;
|
|
114
|
+
return nameMatches(a);
|
|
115
|
+
});
|
|
116
|
+
// An ambiguous `a:b` that matched nothing as `namespace:agent` gets its
|
|
117
|
+
// second reading — `owner:package` — rather than just failing. Order,
|
|
118
|
+
// not guesswork: the old meaning always wins when it resolves.
|
|
119
|
+
if (matches.length === 0 && fqn.kind === "ambiguous") {
|
|
120
|
+
matches = agents.filter(nameMatches);
|
|
121
|
+
}
|
|
122
|
+
if (sharedOnly) {
|
|
123
|
+
// `__shared:x` means the deployment's copy, explicitly. This is the
|
|
124
|
+
// one thing bare-name precedence otherwise takes away: without it,
|
|
125
|
+
// a user with their own copy could never reach the shared one.
|
|
126
|
+
matches = matches.filter(a => a.packageScope !== "user");
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// SHADOWING, at the agent level: the caller's own copy comes first,
|
|
130
|
+
// then everything public. Same rule the registry resolver applies,
|
|
131
|
+
// so "run X" and "show me X" cannot disagree about which X.
|
|
132
|
+
const key = matches.some(a => a.packageScope === "user") ? await callerOwnerKeyOnce() : null;
|
|
133
|
+
matches = [
|
|
134
|
+
...matches.filter(a => a.packageScope === "user" && ownerKeyOf(a.packageOwner) === key && key !== null),
|
|
135
|
+
...matches.filter(a => !(a.packageScope === "user" && ownerKeyOf(a.packageOwner) === key && key !== null)),
|
|
136
|
+
];
|
|
137
|
+
}
|
|
138
|
+
let agent = undefined;
|
|
139
|
+
for (const candidate of matches) {
|
|
140
|
+
if (await visibleToCaller(candidate)) {
|
|
141
|
+
agent = candidate;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!agent)
|
|
146
|
+
return null;
|
|
147
|
+
return {
|
|
148
|
+
name: agent.name,
|
|
149
|
+
prompt: agent.prompt,
|
|
150
|
+
tools: agent.tools ?? undefined,
|
|
151
|
+
initialPrompt: agent.initialPrompt ?? undefined,
|
|
152
|
+
title: agent.title ?? undefined,
|
|
153
|
+
system: agent.system ?? undefined,
|
|
154
|
+
id: agent.id ?? undefined,
|
|
155
|
+
parent: agent.parent ?? undefined,
|
|
156
|
+
splash: agent.splash ?? undefined,
|
|
157
|
+
splashMobile: agent.splashMobile ?? undefined,
|
|
158
|
+
namespace: agent.namespace ?? undefined,
|
|
159
|
+
promptLayerKind: agent.promptLayerKind ?? undefined,
|
|
160
|
+
creatable: agent.creatable ?? !agent.system,
|
|
161
|
+
packageId: agent.packageId ?? undefined,
|
|
162
|
+
packageScope: agent.packageScope ?? undefined,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
31
165
|
/**
|
|
32
166
|
* The session canvas: one reserved artifact per ROOT session, drawn by the
|
|
33
167
|
* agent with draw_canvas and rendered live in the portal. The revision is
|
|
@@ -1056,28 +1190,24 @@ artifactStore) {
|
|
|
1056
1190
|
return sanitized;
|
|
1057
1191
|
};
|
|
1058
1192
|
const isReadOnlyTuner = () => runConfig.agentIdentity === "agent-tuner";
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const candidates = [a.name, a.id, a.title].map(normalizeAgentLookup).filter(Boolean);
|
|
1078
|
-
return candidates.includes(lookup) || (lookupBase && candidates.includes(lookupBase));
|
|
1079
|
-
}) ?? null;
|
|
1080
|
-
};
|
|
1193
|
+
// The one shared resolver — FQN parsing (`__shared:` works here too),
|
|
1194
|
+
// package privacy, and the caller's own-copy shadowing included. The
|
|
1195
|
+
// previous inline copy had none of that: it could hand another user's
|
|
1196
|
+
// private agent to this session and could not address the shared copy
|
|
1197
|
+
// of a shadowed name.
|
|
1198
|
+
const resolveAgentConfigInline = (agentName) => resolveAgentDefinitionForCaller({
|
|
1199
|
+
agentName,
|
|
1200
|
+
userAgents,
|
|
1201
|
+
systemAgents,
|
|
1202
|
+
getCallerOwnerKey: async () => {
|
|
1203
|
+
const owner = catalog
|
|
1204
|
+
? await resolveEffectiveSpawnOwner((id) => catalog.getSession(id), input.sessionId).catch(() => null)
|
|
1205
|
+
: null;
|
|
1206
|
+
return owner?.provider && owner?.subject
|
|
1207
|
+
? `${owner.provider}\u0001${owner.subject}`
|
|
1208
|
+
: null;
|
|
1209
|
+
},
|
|
1210
|
+
});
|
|
1081
1211
|
const loadDirectChildSessions = async () => {
|
|
1082
1212
|
const sdkClient = await getInlineClient();
|
|
1083
1213
|
const sessions = await sdkClient.listSessions();
|
|
@@ -1285,7 +1415,7 @@ artifactStore) {
|
|
|
1285
1415
|
const agentName = String(args.agent_name || "").trim();
|
|
1286
1416
|
if (!agentName)
|
|
1287
1417
|
return "Error: agent_name is required.";
|
|
1288
|
-
const agentDef = resolveAgentConfigInline(agentName);
|
|
1418
|
+
const agentDef = await resolveAgentConfigInline(agentName);
|
|
1289
1419
|
if (!agentDef) {
|
|
1290
1420
|
return `[SYSTEM: create_agent_session failed — agent "${agentName}" not found. Use list_agent_packages / ps_list_agents to see what is installed.]`;
|
|
1291
1421
|
}
|
|
@@ -1410,7 +1540,7 @@ artifactStore) {
|
|
|
1410
1540
|
: "app-agent");
|
|
1411
1541
|
};
|
|
1412
1542
|
if (resolvedAgentName) {
|
|
1413
|
-
const agentDef = resolveAgentConfigInline(resolvedAgentName);
|
|
1543
|
+
const agentDef = await resolveAgentConfigInline(resolvedAgentName);
|
|
1414
1544
|
if (!agentDef) {
|
|
1415
1545
|
return `[SYSTEM: spawn_agent failed — agent "${resolvedAgentName}" not found. Use ps_list_agents to see available agents.]`;
|
|
1416
1546
|
}
|
|
@@ -3149,143 +3279,42 @@ artifactStore) {
|
|
|
3149
3279
|
// Resolves a loaded agent definition by name. User-creatable agents return
|
|
3150
3280
|
// creatable=true. Worker-managed system agents return creatable=false so
|
|
3151
3281
|
// callers can surface a clear error instead of spawning them.
|
|
3282
|
+
//
|
|
3283
|
+
// Matching, package privacy, and owner shadowing all live in
|
|
3284
|
+
// resolveAgentDefinitionForCaller — shared with the control bridge so
|
|
3285
|
+
// spawn_agent/create_agent_session and this activity can never disagree
|
|
3286
|
+
// about which agent a name means.
|
|
3152
3287
|
runtime.registerActivity("resolveAgentConfig", async (_activityCtx, input) => {
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
rawName = input.agentName;
|
|
3183
|
-
}
|
|
3184
|
-
const lookup = normalize(rawName);
|
|
3185
|
-
// Also try without trailing "agent" suffix for fuzzy matching
|
|
3186
|
-
// (LLM often says "Sweeper agent" which normalizes to "sweeperagent", but id is "sweeper")
|
|
3187
|
-
const lookupBase = lookup.replace(/agent$/, "");
|
|
3188
|
-
/**
|
|
3189
|
-
* PACKAGE PRIVACY, enforced at RESOLUTION rather than at creation.
|
|
3190
|
-
*
|
|
3191
|
-
* Workers install every enabled package, including other users'
|
|
3192
|
-
* user-scope ones, so `userAgents` is a fleet-wide list with no
|
|
3193
|
-
* tenancy in it. The Web API filters package agents when a session is
|
|
3194
|
-
* created (`_authorizePackageAgentCreate`), but `spawn_agent` reaches
|
|
3195
|
-
* this activity directly and never passes through that filter — so a
|
|
3196
|
-
* name match alone would hand Alice's private agent, prompt and all,
|
|
3197
|
-
* to Bob's session.
|
|
3198
|
-
*
|
|
3199
|
-
* It has to be enforced HERE and not at spawn time: the return value
|
|
3200
|
-
* carries the agent's full prompt, and merely returning it writes that
|
|
3201
|
-
* prompt into the caller's orchestration history.
|
|
3202
|
-
*
|
|
3203
|
-
* Deployment-owned plugin agents carry no package owner and stay
|
|
3204
|
-
* public, exactly as before.
|
|
3205
|
-
*/
|
|
3206
|
-
let callerOwnerKey = null;
|
|
3207
|
-
let callerResolved = false;
|
|
3208
|
-
const ownerKeyOf = (owner) => owner?.provider && owner?.subject ? `${owner.provider}\u0001${owner.subject}` : null;
|
|
3209
|
-
/** Resolve the caller's owner once per invocation, memoized and fail-closed. */
|
|
3210
|
-
const callerOwnerKeyOnce = async () => {
|
|
3211
|
-
if (!callerResolved) {
|
|
3212
|
-
callerResolved = true;
|
|
3213
|
-
try {
|
|
3214
|
-
const row = input.callerSessionId
|
|
3215
|
-
? await catalog?.getSession(input.callerSessionId)
|
|
3216
|
-
: null;
|
|
3217
|
-
callerOwnerKey = ownerKeyOf(row?.owner);
|
|
3218
|
-
}
|
|
3219
|
-
catch {
|
|
3220
|
-
// Fail closed: an unresolvable caller gets no private agents.
|
|
3221
|
-
callerOwnerKey = null;
|
|
3222
|
-
}
|
|
3223
|
-
}
|
|
3224
|
-
return callerOwnerKey;
|
|
3225
|
-
};
|
|
3226
|
-
const visibleToCaller = async (agent) => {
|
|
3227
|
-
const agentOwnerKey = ownerKeyOf(agent?.packageOwner);
|
|
3228
|
-
if (agent?.packageScope !== "user" || !agentOwnerKey)
|
|
3229
|
-
return true;
|
|
3230
|
-
const key = await callerOwnerKeyOnce();
|
|
3231
|
-
return key !== null && key === agentOwnerKey;
|
|
3232
|
-
};
|
|
3233
|
-
const candidatesFor = (a) => [a.name, a.id, a.title].map(normalize).filter(Boolean);
|
|
3234
|
-
const nameMatches = (a) => {
|
|
3235
|
-
const candidates = candidatesFor(a);
|
|
3236
|
-
return candidates.includes(lookup) || (lookupBase && candidates.includes(lookupBase));
|
|
3237
|
-
};
|
|
3238
|
-
let matches = agents.filter(a => {
|
|
3239
|
-
if (lookupNamespace && normalize(a.namespace) !== normalize(lookupNamespace))
|
|
3240
|
-
return false;
|
|
3241
|
-
return nameMatches(a);
|
|
3288
|
+
return resolveAgentDefinitionForCaller({
|
|
3289
|
+
agentName: input.agentName,
|
|
3290
|
+
userAgents,
|
|
3291
|
+
systemAgents,
|
|
3292
|
+
// PACKAGE PRIVACY, enforced at RESOLUTION rather than at creation.
|
|
3293
|
+
//
|
|
3294
|
+
// Workers install every enabled package, including other users'
|
|
3295
|
+
// user-scope ones, so `userAgents` is a fleet-wide list with no
|
|
3296
|
+
// tenancy in it. The Web API filters package agents when a session
|
|
3297
|
+
// is created (`_authorizePackageAgentCreate`), but `spawn_agent`
|
|
3298
|
+
// reaches this activity directly and never passes through that
|
|
3299
|
+
// filter — so a name match alone would hand Alice's private agent,
|
|
3300
|
+
// prompt and all, to Bob's session.
|
|
3301
|
+
//
|
|
3302
|
+
// It has to be enforced HERE and not at spawn time: the return
|
|
3303
|
+
// value carries the agent's full prompt, and merely returning it
|
|
3304
|
+
// writes that prompt into the caller's orchestration history.
|
|
3305
|
+
//
|
|
3306
|
+
// Deployment-owned plugin agents carry no package owner and stay
|
|
3307
|
+
// public, exactly as before.
|
|
3308
|
+
getCallerOwnerKey: async () => {
|
|
3309
|
+
const row = input.callerSessionId
|
|
3310
|
+
? await catalog?.getSession(input.callerSessionId)
|
|
3311
|
+
: null;
|
|
3312
|
+
const owner = row?.owner;
|
|
3313
|
+
return owner?.provider && owner?.subject
|
|
3314
|
+
? `${owner.provider}\u0001${owner.subject}`
|
|
3315
|
+
: null;
|
|
3316
|
+
},
|
|
3242
3317
|
});
|
|
3243
|
-
// An ambiguous `a:b` that matched nothing as `namespace:agent` gets its
|
|
3244
|
-
// second reading — `owner:package` — rather than just failing. Order,
|
|
3245
|
-
// not guesswork: the old meaning always wins when it resolves.
|
|
3246
|
-
if (matches.length === 0 && fqn.kind === "ambiguous") {
|
|
3247
|
-
matches = agents.filter(nameMatches);
|
|
3248
|
-
}
|
|
3249
|
-
if (sharedOnly) {
|
|
3250
|
-
// `__shared:x` means the deployment's copy, explicitly. This is the
|
|
3251
|
-
// one thing bare-name precedence otherwise takes away: without it,
|
|
3252
|
-
// a user with their own copy could never reach the shared one.
|
|
3253
|
-
matches = matches.filter(a => a.packageScope !== "user");
|
|
3254
|
-
}
|
|
3255
|
-
else {
|
|
3256
|
-
// SHADOWING, at the agent level: the caller's own copy comes first,
|
|
3257
|
-
// then everything public. Same rule the registry resolver applies,
|
|
3258
|
-
// so "run X" and "show me X" cannot disagree about which X.
|
|
3259
|
-
const callerKey = await callerOwnerKeyOnce();
|
|
3260
|
-
matches = [
|
|
3261
|
-
...matches.filter(a => a.packageScope === "user" && ownerKeyOf(a.packageOwner) === callerKey && callerKey !== null),
|
|
3262
|
-
...matches.filter(a => !(a.packageScope === "user" && ownerKeyOf(a.packageOwner) === callerKey && callerKey !== null)),
|
|
3263
|
-
];
|
|
3264
|
-
}
|
|
3265
|
-
let agent = undefined;
|
|
3266
|
-
for (const candidate of matches) {
|
|
3267
|
-
if (await visibleToCaller(candidate)) {
|
|
3268
|
-
agent = candidate;
|
|
3269
|
-
break;
|
|
3270
|
-
}
|
|
3271
|
-
}
|
|
3272
|
-
if (!agent)
|
|
3273
|
-
return null;
|
|
3274
|
-
return {
|
|
3275
|
-
name: agent.name,
|
|
3276
|
-
prompt: agent.prompt,
|
|
3277
|
-
tools: agent.tools ?? undefined,
|
|
3278
|
-
initialPrompt: agent.initialPrompt ?? undefined,
|
|
3279
|
-
title: agent.title ?? undefined,
|
|
3280
|
-
system: agent.system ?? undefined,
|
|
3281
|
-
id: agent.id ?? undefined,
|
|
3282
|
-
parent: agent.parent ?? undefined,
|
|
3283
|
-
splash: agent.splash ?? undefined,
|
|
3284
|
-
splashMobile: agent.splashMobile ?? undefined,
|
|
3285
|
-
namespace: agent.namespace ?? undefined,
|
|
3286
|
-
promptLayerKind: agent.promptLayerKind ?? undefined,
|
|
3287
|
-
creatable: agent.creatable ?? !agent.system,
|
|
3288
|
-
};
|
|
3289
3318
|
});
|
|
3290
3319
|
// ── spawnChildSession ─────────────────────────────────────
|
|
3291
3320
|
// Creates a child session via the PilotSwarmClient SDK.
|