@sema-agent/server 7.129.1 → 7.131.0
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/MIGRATION.md +102 -0
- package/README.md +8 -5
- package/README.zh-CN.md +6 -4
- package/USAGE.md +48 -9
- package/dist/audit.js +4 -1
- package/dist/bench/s1/live-deps.js +2 -0
- package/dist/boot/resolve-spec.js +43 -16
- package/dist/boot/stage-02-web-admin-pull.js +9 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stage-10-http-server.js +7 -2
- package/dist/capabilities/builtin-tools.js +2 -1
- package/dist/capabilities/composite-members.d.ts +1 -0
- package/dist/capabilities/composite-members.js +1 -0
- package/dist/capabilities/hands-lane.js +2 -2
- package/dist/capabilities/host-tool-names.d.ts +17 -0
- package/dist/capabilities/host-tool-names.js +15 -0
- package/dist/capabilities/mcp-probe.js +19 -6
- package/dist/capabilities/repo-tools.js +4 -3
- package/dist/capabilities/scenarios.d.ts +34 -0
- package/dist/capabilities/scenarios.js +20 -4
- package/dist/capabilities/select-environment-tool.js +2 -1
- package/dist/capabilities/send-user-file-tool.d.ts +0 -1
- package/dist/capabilities/send-user-file-tool.js +2 -2
- package/dist/capabilities/skills.js +1 -1
- package/dist/capabilities/tool-defer.js +2 -1
- package/dist/config-catalog.js +16 -5
- package/dist/config-invariants.js +2 -2
- package/dist/config-provider.js +2 -2
- package/dist/config-types.d.ts +6 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +18 -17
- package/dist/elicitation.d.ts +7 -0
- package/dist/elicitation.js +20 -5
- package/dist/exec-bounds.d.ts +6 -6
- package/dist/hooks/hook-llm.d.ts +11 -1
- package/dist/hooks/hook-llm.js +11 -14
- package/dist/hooks/hook-runner.d.ts +55 -9
- package/dist/hooks/hook-runner.js +86 -16
- package/dist/http/admission.js +2 -2
- package/dist/http/approval-sweeps.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +12 -11
- package/dist/http/checkpoint-ctx.js +4 -4
- package/dist/http/dispatch.js +1 -1
- package/dist/http/resume-legs.js +6 -5
- package/dist/http/routes/approvals-assistant.js +1 -1
- package/dist/http/routes/capabilities.js +13 -5
- package/dist/http/routes/runs.js +18 -14
- package/dist/http/routes/sessions.js +22 -10
- package/dist/http/routes/tasks.js +16 -11
- package/dist/http/scenario-read-face.d.ts +14 -0
- package/dist/http/scenario-read-face.js +5 -0
- package/dist/http/send.d.ts +10 -0
- package/dist/http/send.js +6 -0
- package/dist/http/server.d.ts +16 -4
- package/dist/http/server.js +2 -2
- package/dist/http/stored-body-replay.d.ts +10 -3
- package/dist/http/stored-body-replay.js +27 -6
- package/dist/http/wire-types.d.ts +18 -5
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mcp-assertion-header.d.ts +2 -2
- package/dist/mcp-assertion-header.js +3 -3
- package/dist/model-entry-refusal.d.ts +1 -7
- package/dist/model-route-endpoint.d.ts +51 -11
- package/dist/model-route-endpoint.js +45 -23
- package/dist/model-route-gap.d.ts +5 -4
- package/dist/model-route-gap.js +1 -1
- package/dist/model-select.d.ts +58 -0
- package/dist/model-select.js +31 -0
- package/dist/namespaced-rule-name.d.ts +7 -4
- package/dist/namespaced-rule-name.js +4 -3
- package/dist/observability/fail-open.d.ts +16 -7
- package/dist/observability/fail-open.js +10 -2
- package/dist/plugins/local-session-store.js +2 -2
- package/dist/plugins/one-time-migrations.d.ts +21 -1
- package/dist/plugins/one-time-migrations.js +33 -1
- package/dist/plugins/remote-env-adb.js +5 -4
- package/dist/plugins/remote-env-local-docker.js +5 -4
- package/dist/plugins/remote-env-ssh.js +5 -4
- package/dist/question.d.ts +18 -9
- package/dist/question.js +21 -13
- package/dist/request-key-closure.d.ts +2 -2
- package/dist/request-key-closure.js +16 -9
- package/dist/rule-entry-list.d.ts +124 -17
- package/dist/rule-entry-list.js +140 -24
- package/dist/run-local.d.ts +3 -3
- package/dist/run-local.js +8 -7
- package/dist/runs.d.ts +42 -2
- package/dist/runs.js +45 -2
- package/dist/runtime-caps-resolver.d.ts +7 -0
- package/dist/runtime-caps-resolver.js +1 -0
- package/dist/session-titler.d.ts +4 -1
- package/dist/session-titler.js +13 -3
- package/dist/spec-fields.d.ts +8 -4
- package/dist/stored-request-hooks-m20.d.ts +23 -0
- package/dist/stored-request-hooks-m20.js +124 -0
- package/dist/stored-request-tool-axes-m19.d.ts +29 -0
- package/dist/stored-request-tool-axes-m19.js +27 -0
- package/dist/task-mcp.d.ts +55 -60
- package/dist/task-mcp.js +197 -96
- package/dist/task-settings.d.ts +41 -5
- package/dist/task-settings.js +39 -25
- package/dist/tool-axis-words.d.ts +14 -17
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.d.ts +29 -0
- package/dist/trace/ledger-sink.js +11 -0
- package/dist/trace/project.d.ts +6 -0
- package/dist/trace/project.js +11 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/user-authored-text.d.ts +7 -0
- package/dist/user-authored-text.js +3 -0
- package/dist/web-admin-client/apply-effective.js +10 -5
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +3 -2
|
@@ -2,10 +2,11 @@ import { realpathSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, deriveAskEffective, expandTiers, modePolicyOf, resolveAskSeamForm } from "@sema-agent/core";
|
|
4
4
|
import { cappedCeiling } from "../budget.js";
|
|
5
|
+
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
6
|
import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/center-prompts.js";
|
|
6
7
|
import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
|
|
7
8
|
import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
8
|
-
import {
|
|
9
|
+
import { createScenarioGate, mergeUserSkills, selectScenario } from "../capabilities/scenarios.js";
|
|
9
10
|
import { stampHandsLane, toolPolicyForHands } from "../capabilities/hands-lane.js";
|
|
10
11
|
import { buildNoticeLaneFacts, noPersistentMemoryNotice, withNoPersistentMemoryNotice } from "../capabilities/memory-notice.js";
|
|
11
12
|
import { applyLongtailDefer } from "../capabilities/tool-defer.js";
|
|
@@ -35,9 +36,11 @@ import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, norma
|
|
|
35
36
|
import { isAbsoluteHostPath } from "@sema-agent/settings-schema";
|
|
36
37
|
import { cwdHonored, deviceCwdHonored, effectiveHostWorkspace, inProcessSingleUserLane, parseAdditionalDirectories, satisfiedByProcessCwd, shellEnvMismatchCount } from "../task-cwd.js";
|
|
37
38
|
import { assertRequestA2aUnlocked, resolveRequestA2a } from "../task-a2a.js";
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
39
|
+
import { assertRequestMcpEntries, assertRequestMcpUnlocked, resolveRequestMcp } from "../task-mcp.js";
|
|
40
|
+
import { storedToolAxesRereadM19 } from "../stored-request-tool-axes-m19.js";
|
|
41
|
+
import { storedHooksRereadM20 } from "../stored-request-hooks-m20.js";
|
|
42
|
+
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, REQUEST_SETTINGS_REFUSAL_PREFIX, acceptAppendSystemPrompt, applyTaskSettings, effectivePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, taskHooksHonored, providerDropsAppend, settingsPermissionBucketIssue, withPermissionMode } from "../task-settings.js";
|
|
43
|
+
import { inertToolListEntryOf } from "../rule-entry-list.js";
|
|
41
44
|
import { TENANCY_WALL_CLOCK_BASE_SEC, enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
42
45
|
import { redactSecrets } from "../trace/redact.js";
|
|
43
46
|
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
@@ -47,6 +50,7 @@ import { withUnattendedLeg } from "../unattended-permission-mode.js";
|
|
|
47
50
|
import { memoryFaceMounted } from "../memory-scope.js";
|
|
48
51
|
export function createResolveSpec(ctx) {
|
|
49
52
|
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, fenceSessionOwner, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, approverSeat, lockedKeys, } = ctx;
|
|
53
|
+
const scenarioGate = createScenarioGate({ config, scenarios, principalCaps, logger }, "enforce");
|
|
50
54
|
assertGuardPatternsUsable(config);
|
|
51
55
|
const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, {
|
|
52
56
|
approvalSeat: durableEnabled ? "durable" : "absent",
|
|
@@ -69,9 +73,7 @@ export function createResolveSpec(ctx) {
|
|
|
69
73
|
throw createCodedHttpError(501, "capability.checkpoint_store_required", seatRefusal);
|
|
70
74
|
assertRequestMcpUnlocked(body.mcpServers, lockedKeys);
|
|
71
75
|
if (opts?.leg === "fresh")
|
|
72
|
-
|
|
73
|
-
if (opts?.leg === "fresh")
|
|
74
|
-
assertRequestMcpIdentityHeaders(body.mcpServers);
|
|
76
|
+
assertRequestMcpEntries(body.mcpServers);
|
|
75
77
|
if (opts?.leg === "fresh") {
|
|
76
78
|
const conflict = scratchpadCoveringTaskRoot({
|
|
77
79
|
cwd: body.cwd,
|
|
@@ -111,9 +113,21 @@ export function createResolveSpec(ctx) {
|
|
|
111
113
|
throw new HttpError(400, bucketIssue.message, { code: "request.field_invalid", extra: bucketIssue.fields });
|
|
112
114
|
}
|
|
113
115
|
{
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
116
|
+
const replay = opts?.leg === "resume";
|
|
117
|
+
const inertList = inertToolListEntryOf(body, replay ? ["deny"] : undefined);
|
|
118
|
+
if (inertList !== undefined)
|
|
119
|
+
throw new HttpError(400, inertList.message, { code: inertList.code, ...(inertList.extra !== undefined ? { extra: { ...inertList.extra } } : {}) });
|
|
120
|
+
if (replay) {
|
|
121
|
+
const stale = inertToolListEntryOf(body, ["allow"]);
|
|
122
|
+
if (stale !== undefined) {
|
|
123
|
+
logger.warn("stored_allow_list_inert_resumed", {
|
|
124
|
+
sessionId: auth?.sessionId ?? null,
|
|
125
|
+
layer: stale.layer,
|
|
126
|
+
sentence: stale.message,
|
|
127
|
+
note: "this resumed task's stored request carries an allow-list entry the engine reads as nothing (a retired name, a case twin, an unreadable spelling or a content form): the entry stays as written and grants nothing — the task ran that way before the upgrade, so it continues; fix the entry in a new submission to get what it was meant to allow",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
117
131
|
}
|
|
118
132
|
assertRequestA2aUnlocked(body.a2aPeers, lockedKeys);
|
|
119
133
|
const gated = await gateScenarioAndAppend(body, auth, opts);
|
|
@@ -136,7 +150,7 @@ export function createResolveSpec(ctx) {
|
|
|
136
150
|
return stamped;
|
|
137
151
|
};
|
|
138
152
|
const gateScenarioAndAppend = async (body, auth, opts) => {
|
|
139
|
-
const scenarioName =
|
|
153
|
+
const scenarioName = await scenarioGate(auth?.principal, body.scenario);
|
|
140
154
|
gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), executionLaneOf(config.remoteExec?.provider), logger);
|
|
141
155
|
const cap = selectScenario(scenarios, scenarioName)(body, auth?.principal, auth?.sessionId !== undefined ? { sessionId: auth.sessionId } : {});
|
|
142
156
|
const centerDecls = getCenterPrompts()?.declarations;
|
|
@@ -145,7 +159,7 @@ export function createResolveSpec(ctx) {
|
|
|
145
159
|
: providerDropsAppend(cap.promptProvider, typeof body.systemPrompt === "string" ? body.systemPrompt : undefined);
|
|
146
160
|
const acceptedAppend = acceptAppendSystemPrompt(body.appendSystemPrompt, (detail) => logger.warn("append_system_prompt_dropped", { detail, sessionId: auth?.sessionId ?? null }), appendLessPack);
|
|
147
161
|
const objective = typeof body.objective === "string" ? body.objective : "";
|
|
148
|
-
const parsedSettings = parseTaskSettings(body.settings);
|
|
162
|
+
const parsedSettings = parseTaskSettings(body.settings, opts?.leg === "fresh" || !taskHooksHonored(config) ? {} : { storedHooksReread: storedHooksRereadM20(new Date().toISOString().slice(0, 10)) });
|
|
149
163
|
if (opts?.leg === "fresh" && appendLessPack) {
|
|
150
164
|
const riderPresent = typeof body.appendSystemPrompt === "string" && body.appendSystemPrompt.length > 0;
|
|
151
165
|
if (riderPresent || parsedSettings.settings?.outputStyle) {
|
|
@@ -155,6 +169,17 @@ export function createResolveSpec(ctx) {
|
|
|
155
169
|
if (parsedSettings.deferred.length > 0) {
|
|
156
170
|
logger.warn("task_settings_deferred", { fields: parsedSettings.deferred, sessionId: auth?.sessionId ?? null });
|
|
157
171
|
}
|
|
172
|
+
if (parsedSettings.hooksReread !== undefined) {
|
|
173
|
+
logger.warn("hooks_read_as_before_upgrade", {
|
|
174
|
+
migration: "M20",
|
|
175
|
+
sessionId: auth?.sessionId ?? null,
|
|
176
|
+
stripped: parsedSettings.hooksReread.stripped,
|
|
177
|
+
note: "this replayed request was stored before the upgrade and its settings.hooks carries a shape the hooks reader now refuses (a key the reader does not know that hides a guard-event member, or a JSON `__proto__` event key); while one-time migration M20 is open the keys named in `stripped` are dropped — what the pre-upgrade reader dropped — and the rest goes through the same reader again, so the guard hooks keep running; once the window closes the same request has its whole settings.hooks left out",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
if (parsedSettings.deferred.includes("hooks") && taskHooksHonored(config)) {
|
|
181
|
+
recordFailOpen("server.hooks.resume-reader-refused", `leg=${opts?.leg ?? "resume"}`);
|
|
182
|
+
}
|
|
158
183
|
let attachmentNotice;
|
|
159
184
|
{
|
|
160
185
|
const reqAtt = body.attachmentIds;
|
|
@@ -285,6 +310,7 @@ export function createResolveSpec(ctx) {
|
|
|
285
310
|
}
|
|
286
311
|
return { taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked };
|
|
287
312
|
};
|
|
313
|
+
const NO_ANCHOR_HINT = "no rewind anchor is recorded under that handle (a prompt that carried attachments records none when its attachment frames were interrupted or their count did not match)";
|
|
288
314
|
const resolveHistoryAnchors = async (body, auth) => {
|
|
289
315
|
let resumeAtEntryId;
|
|
290
316
|
if (typeof body.resumeAt === "string" && body.resumeAt.length > 0) {
|
|
@@ -294,7 +320,7 @@ export function createResolveSpec(ctx) {
|
|
|
294
320
|
throw new HttpError(501, "resume-at is not available on this worker (no session-store backend for the anchor map)");
|
|
295
321
|
resumeAtEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.resumeAt, auth.principal ?? null);
|
|
296
322
|
if (resumeAtEntryId === undefined)
|
|
297
|
-
throw new HttpError(404,
|
|
323
|
+
throw new HttpError(404, `resumeAt: no such message in this session (resume_at.unknown_event) — ${NO_ANCHOR_HINT}; give an explicit resumeAt`, { code: "resume_at.unknown_event" });
|
|
298
324
|
}
|
|
299
325
|
let rewindFilesToEntryId;
|
|
300
326
|
if (resumeAtEntryId === undefined && typeof body.rewindFilesTo === "string" && body.rewindFilesTo.length > 0) {
|
|
@@ -304,7 +330,7 @@ export function createResolveSpec(ctx) {
|
|
|
304
330
|
throw new HttpError(501, "rewind-files-to is not available on this worker (no session-store backend for the anchor map)");
|
|
305
331
|
rewindFilesToEntryId = await resumeAnchorStore.resolve(auth.sessionId, body.rewindFilesTo, auth.principal ?? null);
|
|
306
332
|
if (rewindFilesToEntryId === undefined)
|
|
307
|
-
throw new HttpError(404,
|
|
333
|
+
throw new HttpError(404, `rewindFilesTo: no such message in this session (rewind_files_to.unknown_event) — ${NO_ANCHOR_HINT}; name another prompt`, { code: "rewind_files_to.unknown_event" });
|
|
308
334
|
}
|
|
309
335
|
const s4ProjectId = auth?.resolvedProjectId ?? (typeof body.projectId === "string" && body.projectId ? body.projectId.toLowerCase() : undefined);
|
|
310
336
|
const s4DefaultScopes = s4ProjectId ? config.projects[s4ProjectId]?.defaultScopes : undefined;
|
|
@@ -334,6 +360,7 @@ export function createResolveSpec(ctx) {
|
|
|
334
360
|
sessionId: auth?.sessionId,
|
|
335
361
|
principal: auth?.principal,
|
|
336
362
|
images: body.images,
|
|
363
|
+
...(body.userAttachments !== undefined ? { userAttachments: body.userAttachments } : {}),
|
|
337
364
|
...(body.clientContext ? { clientContext: { timeZone: body.clientContext.timeZone, userEmail: body.clientContext.userEmail } } : {}),
|
|
338
365
|
...(() => {
|
|
339
366
|
const ct = body.compaction?.clampTolerance;
|
|
@@ -456,7 +483,7 @@ export function createResolveSpec(ctx) {
|
|
|
456
483
|
...(() => { const v = toolNameListFromBody(body.userInvokedSkills); return v ? { userInvokedSkills: v } : {}; })(),
|
|
457
484
|
...(parsedSettings.settings?.skillListingBudgetFraction !== undefined ? { skillListingBudgetFraction: parsedSettings.settings.skillListingBudgetFraction } : {}),
|
|
458
485
|
...(parsedSettings.settings?.skillListingMaxDescChars !== undefined ? { skillListingMaxDescChars: parsedSettings.settings.skillListingMaxDescChars } : {}),
|
|
459
|
-
mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined),
|
|
486
|
+
mcp: resolveRequestMcp(mcpForScenario(config.mcpServers, scenarioName), body.mcpServers, config, logger, opts?.onNotice !== undefined ? { sessionId: auth?.sessionId, emit: opts.onNotice } : undefined, opts?.leg === "fresh" ? undefined : storedToolAxesRereadM19(new Date().toISOString().slice(0, 10))),
|
|
460
487
|
a2a: resolveRequestA2a(a2aForScenario(config.a2aPeers, scenarioName), body.a2aPeers, config, logger),
|
|
461
488
|
promptProvider: centerDecls ? centerPromptProvider(centerDecls, scenarioName) : cap.promptProvider,
|
|
462
489
|
toolPolicy: toolPolicyForHands(hands, durableEnabled
|
|
@@ -596,7 +623,7 @@ export function createResolveSpec(ctx) {
|
|
|
596
623
|
...(e.detail !== undefined ? { extra: { writeExemptDirCoversRoot: e.detail } } : {}),
|
|
597
624
|
});
|
|
598
625
|
}
|
|
599
|
-
throw new HttpError(422,
|
|
626
|
+
throw new HttpError(422, `${REQUEST_SETTINGS_REFUSAL_PREFIX}${e.message}`);
|
|
600
627
|
}
|
|
601
628
|
}
|
|
602
629
|
const notice = noPersistentMemoryNotice({
|
|
@@ -4,6 +4,9 @@ import { composeExecutionLane, FILE_SOURCED_LANES } from "./execution-lane.js";
|
|
|
4
4
|
import { serviceConfigSourceKind } from "../config-provider.js";
|
|
5
5
|
import { logConfigDirMigration, migrateLocalConfigDirectory } from "../config-d-migration.js";
|
|
6
6
|
import { defaultModelRefusal, refusedNameMintedRefusal } from "../model-entry-refusal.js";
|
|
7
|
+
import { isRosterGateArmed } from "../config.js";
|
|
8
|
+
import { judgeServedModelRoute } from "../model-route-endpoint.js";
|
|
9
|
+
import { servedModelTableLeg } from "../model-select.js";
|
|
7
10
|
export async function runStage02(ctx) {
|
|
8
11
|
const { config, localRoot, logger, metrics } = ctx;
|
|
9
12
|
const configSource = serviceConfigSourceKind(config);
|
|
@@ -11,10 +14,15 @@ export async function runStage02(ctx) {
|
|
|
11
14
|
logConfigDirMigration(logger, await migrateLocalConfigDirectory(localRoot));
|
|
12
15
|
await composeExecutionLane(config, { configSource, localRoot, logger, drivesFromFile: FILE_SOURCED_LANES });
|
|
13
16
|
const webAdmin = await createWebAdminRuntime({ config, logger, metrics, localRoot });
|
|
14
|
-
const
|
|
17
|
+
const servedLeg = servedModelTableLeg(config);
|
|
18
|
+
const noDefaultModel = defaultModelRefusal(config, servedLeg);
|
|
15
19
|
if (noDefaultModel !== undefined) {
|
|
16
20
|
throw new Error(noDefaultModel);
|
|
17
21
|
}
|
|
22
|
+
const routeVerdict = judgeServedModelRoute(config, { envTableIsPlaceholder: isRosterGateArmed(config.webAdmin) });
|
|
23
|
+
if (routeVerdict !== undefined) {
|
|
24
|
+
throw new Error(routeVerdict.reason);
|
|
25
|
+
}
|
|
18
26
|
const mintedRefused = refusedNameMintedRefusal(config);
|
|
19
27
|
if (mintedRefused !== undefined) {
|
|
20
28
|
throw new Error(mintedRefused);
|
|
@@ -56,7 +56,7 @@ export declare function runStage10(ctx: Awaited<ReturnType<typeof runStage09>>):
|
|
|
56
56
|
resumeAnchorStore: import("../plugins/store-backend.js").ResumeAnchorStore | undefined;
|
|
57
57
|
runStore: import("../plugins/store-backend.js").RunStore | undefined;
|
|
58
58
|
scenarioDetails: Record<string, import("../capabilities/scenarios.js").ScenarioDetail>;
|
|
59
|
-
scenarios: Record<string, import("../
|
|
59
|
+
scenarios: Record<string, import("../capabilities/scenarios.js").Scenario>;
|
|
60
60
|
selectEnvTool: import("@sema-agent/core").ToolSpec<import("@sema-agent/core").TSchema> | undefined;
|
|
61
61
|
taskLimitCaps: {
|
|
62
62
|
timeoutSec: number | undefined;
|
|
@@ -19,6 +19,7 @@ import { mintEngineSignal } from "../fleet/engine-signal.js";
|
|
|
19
19
|
import { createServingLeaseClient } from "../fleet/serving-lease.js";
|
|
20
20
|
import { fleetFenceLaneOf } from "../config.js";
|
|
21
21
|
import { createResolveSpec } from "./resolve-spec.js";
|
|
22
|
+
import { createScenarioGate } from "../capabilities/scenarios.js";
|
|
22
23
|
import { executionLaneOf } from "../execution-lane-caps.js";
|
|
23
24
|
import { startFleetReconciler } from "../fleet/fleet-reconciler.js";
|
|
24
25
|
import { createSideQueryLane } from "./side-query-lane.js";
|
|
@@ -210,7 +211,8 @@ export async function runStage10(ctx) {
|
|
|
210
211
|
});
|
|
211
212
|
const runnerFor = createHandsRunnerSelector({ full: runner, handsless: handslessRunner });
|
|
212
213
|
const sideQuery = createSideQueryLane({ brain, config, getKeyResolver: () => webAdmin.getKeyResolver() });
|
|
213
|
-
const
|
|
214
|
+
const scenarioGate = createScenarioGate({ config, scenarios, principalCaps, logger }, "peek");
|
|
215
|
+
const server = createHttpServer({ runner, runnerFor, sideQuery, scenarioGate, config, resolveSpec, stores, coordinators, seams, observability, governance, deployment, knobs });
|
|
214
216
|
bindServerLateSeats({ config, logger, server });
|
|
215
217
|
const fleetReconcile = startFleetReconciler({
|
|
216
218
|
bus: fleetBus,
|
|
@@ -282,8 +284,11 @@ export async function runStage10(ctx) {
|
|
|
282
284
|
const failoverFirstByte = buildFailoverFirstByteNotice(config);
|
|
283
285
|
if (failoverFirstByte)
|
|
284
286
|
logger.warn("failover_first_byte_lane", failoverFirstByte);
|
|
287
|
+
const bound = server.address();
|
|
288
|
+
if (bound === null || typeof bound === "string")
|
|
289
|
+
throw new Error(`http server listening without a TCP address (server.address() = ${JSON.stringify(bound)})`);
|
|
285
290
|
logger.info("listening", {
|
|
286
|
-
port:
|
|
291
|
+
port: bound.port,
|
|
287
292
|
bindHost: bindHost ?? "0.0.0.0/::(all interfaces)",
|
|
288
293
|
...(bindHost === "127.0.0.1" && !config.bindHost
|
|
289
294
|
? { bindHostNote: "auto-narrowed to loopback: write face is unauthenticated (ALLOW_UNAUTHED_WRITES with no service token). Set BIND_HOST explicitly to override." }
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { NOW_TOOL_NAME } from "./host-tool-names.js";
|
|
2
3
|
export function nowTool() {
|
|
3
4
|
return {
|
|
4
|
-
name:
|
|
5
|
+
name: NOW_TOOL_NAME,
|
|
5
6
|
description: "Return the current date-time as an ISO-8601 UTC string. Use when the task involves dates/deadlines/'today'.",
|
|
6
7
|
effect: "read",
|
|
7
8
|
parameters: Type.Object({}),
|
|
@@ -54,6 +54,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
|
|
|
54
54
|
readonly workflowsGate: readonly ["engineCan", "denial"];
|
|
55
55
|
readonly sealedKey: readonly ["alg", "publicKeyId", "publicKey"];
|
|
56
56
|
readonly workspaceSync: readonly ["persist", "keep", "timeoutMs", "cache"];
|
|
57
|
+
readonly userAttachments: readonly ["entries", "kinds", "supplies", "limits"];
|
|
57
58
|
};
|
|
58
59
|
type CompositeTable = typeof COMPOSITE_CAPABILITY_MEMBERS;
|
|
59
60
|
/** 值的**具名成员对象臂**(标量 / 字面词 / `false` / `null` / `undefined` / 数组 / 函数臂剥掉)。 */
|
|
@@ -15,6 +15,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
|
|
|
15
15
|
workflowsGate: ["engineCan", "denial"],
|
|
16
16
|
sealedKey: ["alg", "publicKeyId", "publicKey"],
|
|
17
17
|
workspaceSync: ["persist", "keep", "timeoutMs", "cache"],
|
|
18
|
+
userAttachments: ["entries", "kinds", "supplies", "limits"],
|
|
18
19
|
};
|
|
19
20
|
export function gateCompositeMembers(body) {
|
|
20
21
|
return body;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { handBandEffects, judgeRuleToolName, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
|
|
2
|
-
import {
|
|
2
|
+
import { TOOL_NAME_ROSTER } from "../namespaced-rule-name.js";
|
|
3
3
|
export function withoutHands(deps) {
|
|
4
4
|
const { executionEnvFactory: _handsDropped, lspManager: _lspDropped, ...rest } = deps;
|
|
5
5
|
return rest;
|
|
@@ -20,7 +20,7 @@ export const HANDS_BAND_TOOL_NAMES = Object.freeze([
|
|
|
20
20
|
"EnterWorktree",
|
|
21
21
|
"ExitWorktree",
|
|
22
22
|
"ListAgents",
|
|
23
|
-
].filter((n, i, all) => judgeRuleToolName(n,
|
|
23
|
+
].filter((n, i, all) => judgeRuleToolName(n, TOOL_NAME_ROSTER).kind === "current" && all.indexOf(n) === i));
|
|
24
24
|
export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript", "ListAgents"]);
|
|
25
25
|
export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
|
|
26
26
|
export function handsDenyPolicy() {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* **本仓自己铸给模型的工具的名字** —— 唯一的名字源(叶模块,零依赖)。铸它们的地方(`builtin-tools.ts` / `send-user-file-tool.ts` / `select-environment-tool.ts` / `repo-tools.ts`)
|
|
3
|
+
* 与读它们的地方(工具名清单的受理判:`rule-entry-list.ts` 的名册;场景详情的工具名单;长尾延迟披露集)都从这里取,不手抄。
|
|
4
|
+
*
|
|
5
|
+
* 为什么受理口要知道它们:请求的 `allowTools` / `excludeTools` / 子代的 `allowTools` / `denyTools` 里把 `SendUserFile` 写成 `SENDUSERFILE`,引擎只在开跑时按这一 run 真挂着的名册告运维;
|
|
6
|
+
* 受理口读不到那份名册,读得到的是「引擎目录 ∪ 本仓自铸的这几只」—— 不把这几只放进名册,它们的孪生就是受理口漏掉的同一类静默失效。
|
|
7
|
+
* 宿主之外的调用方工具、MCP / A2A 对端的工具不在其中(前者本仓不挂,后者是协议名,由协议段判)。
|
|
8
|
+
*/
|
|
9
|
+
export declare const NOW_TOOL_NAME = "Now";
|
|
10
|
+
export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
11
|
+
export declare const SELECT_ENVIRONMENT_TOOL_NAME = "SelectEnvironment";
|
|
12
|
+
export declare const REPO_TREE_TOOL_NAME = "repo_tree";
|
|
13
|
+
export declare const REPO_READ_FILE_TOOL_NAME = "repo_read_file";
|
|
14
|
+
export declare const REPO_PULL_DIFF_TOOL_NAME = "repo_pull_diff";
|
|
15
|
+
/** 本仓自铸的全部工具名(受理口工具名清单的名册成员;加一只自铸工具 ⇒ 这里加一行)。 */
|
|
16
|
+
export declare const HOST_TOOL_NAMES: ReadonlySet<string>;
|
|
17
|
+
//# sourceMappingURL=host-tool-names.d.ts.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const NOW_TOOL_NAME = "Now";
|
|
2
|
+
export const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
3
|
+
export const SELECT_ENVIRONMENT_TOOL_NAME = "SelectEnvironment";
|
|
4
|
+
export const REPO_TREE_TOOL_NAME = "repo_tree";
|
|
5
|
+
export const REPO_READ_FILE_TOOL_NAME = "repo_read_file";
|
|
6
|
+
export const REPO_PULL_DIFF_TOOL_NAME = "repo_pull_diff";
|
|
7
|
+
export const HOST_TOOL_NAMES = new Set([
|
|
8
|
+
NOW_TOOL_NAME,
|
|
9
|
+
SEND_USER_FILE_TOOL_NAME,
|
|
10
|
+
SELECT_ENVIRONMENT_TOOL_NAME,
|
|
11
|
+
REPO_TREE_TOOL_NAME,
|
|
12
|
+
REPO_READ_FILE_TOOL_NAME,
|
|
13
|
+
REPO_PULL_DIFF_TOOL_NAME,
|
|
14
|
+
]);
|
|
15
|
+
//# sourceMappingURL=host-tool-names.js.map
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { probeMcpServers } from "@sema-agent/core";
|
|
1
|
+
import { probeMcpServers, readMcpToolAxes } from "@sema-agent/core";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { createFixedWindowRateLimiter } from "../device-enrollment.js";
|
|
4
4
|
import { canonicalJson } from "../canonical-json.js";
|
|
5
5
|
import { echoKeyName, keyClosureClause, scanKeyClosure } from "../request-key-closure.js";
|
|
6
6
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
7
|
-
import { HTTP_MCP_TRANSPORT_KEYS, MAX_REQUEST_MCP_SERVERS, MCP_ENTRY_EMPTY_ARRAY_KEYS, MCP_ENTRY_EMPTY_RECORD_KEYS, MCP_SERVER_ENTRY_KEYS, MCP_TRANSPORT_EMPTY_ARRAY_KEYS, MCP_TRANSPORT_EMPTY_RECORD_KEYS, STDIO_MCP_TRANSPORT_KEYS, requestEntryIdentityRefusal, validateRequestMcp, } from "../task-mcp.js";
|
|
7
|
+
import { HTTP_MCP_TRANSPORT_KEYS, MAX_REQUEST_MCP_SERVERS, MCP_ENTRY_EMPTY_ARRAY_KEYS, MCP_ENTRY_EMPTY_RECORD_KEYS, MCP_SERVER_ENTRY_KEYS, MCP_TRANSPORT_EMPTY_ARRAY_KEYS, MCP_TRANSPORT_EMPTY_RECORD_KEYS, STDIO_MCP_TRANSPORT_KEYS, requestEntryIdentityRefusal, requestEntryToolAxesRefusal, validateRequestMcp, } from "../task-mcp.js";
|
|
8
8
|
import { toCoreMcpServerSpec } from "../mcp-assertion-header.js";
|
|
9
9
|
import { buildDisplaySafeUrl } from "../trace/redact.js";
|
|
10
10
|
import { projectWiringManifestMcpRows } from "../trace/project.js";
|
|
@@ -53,7 +53,7 @@ function entryKeyIssue(entry, at) {
|
|
|
53
53
|
scanKeyClosure(t, `${at}.transport.`, new Set(keys), NO_TOLERATED_KEYS, buckets);
|
|
54
54
|
}
|
|
55
55
|
if (buckets.unknown.length === 0) {
|
|
56
|
-
const refusal = requestEntryIdentityRefusal(entry);
|
|
56
|
+
const refusal = requestEntryIdentityRefusal(entry, at);
|
|
57
57
|
if (refusal === undefined)
|
|
58
58
|
return null;
|
|
59
59
|
return refusal.errorCode === "request.body_shape" ? shapeRefusal(refusal.message) : fieldRefusal(refusal.message);
|
|
@@ -64,6 +64,9 @@ function entryKeyIssue(entry, at) {
|
|
|
64
64
|
`Accepted per entry: ${[...MCP_SERVER_ENTRY_KEYS].sort().join(",")} (transport.kind "stdio": ${[...STDIO_MCP_TRANSPORT_KEYS].sort().join(",")}; "http": ${[...HTTP_MCP_TRANSPORT_KEYS].sort().join(",")})`, buckets.unknown);
|
|
65
65
|
}
|
|
66
66
|
function entryValueIssue(entry, at) {
|
|
67
|
+
const axesRefusal = requestEntryToolAxesRefusal(entry, at);
|
|
68
|
+
if (axesRefusal !== undefined)
|
|
69
|
+
return fieldRefusal(axesRefusal.message);
|
|
67
70
|
const read = validateRequestMcp([entry]);
|
|
68
71
|
if (read === null)
|
|
69
72
|
return fieldRefusal(`${at} is not a readable MCP declaration`);
|
|
@@ -71,7 +74,7 @@ function entryValueIssue(entry, at) {
|
|
|
71
74
|
if (dropped !== undefined || read.ok.length !== 1) {
|
|
72
75
|
const field = dropped?.field ?? "entry";
|
|
73
76
|
return fieldRefusal(`${at}.${field} is not a usable value${urlEchoFor(entry, field)} — this endpoint refuses the whole list rather than dialing a silently shortened one` +
|
|
74
|
-
` (a
|
|
77
|
+
` (a probe answering "can I use these" must not quietly answer about fewer)`);
|
|
75
78
|
}
|
|
76
79
|
if (read.badContentOrigin.length > 0) {
|
|
77
80
|
return fieldRefusal(`${at}.contentOrigin must be one of ${CONTENT_ORIGIN_WORDS.map((w) => `"${w}"`).join(" | ")} — got ${describeBadContentOrigin(entry.contentOrigin)} (omit the key for the default: this server's tools classify as external content)`);
|
|
@@ -82,12 +85,22 @@ function entryValueIssue(entry, at) {
|
|
|
82
85
|
function declarationDowngradeIssue(entry, spec, at) {
|
|
83
86
|
if (spec === undefined)
|
|
84
87
|
return fieldRefusal(`${at} is not a readable MCP declaration`);
|
|
85
|
-
const diff = jsonPathDiff(stripEquivalentEmpties(entry), stripEquivalentEmpties(spec), at);
|
|
88
|
+
const diff = jsonPathDiff(stripEquivalentEmpties(declaredAsJudged(entry)), stripEquivalentEmpties(spec), at);
|
|
86
89
|
if (diff.length === 0)
|
|
87
90
|
return null;
|
|
88
91
|
diff.sort();
|
|
89
92
|
return fieldRefusal(`${keyClosureClause(diff, "unusable value(s)")} — this deployment's declaration reader could not use these exactly as written, and a probe must not dial a configuration that differs from the one you declared` +
|
|
90
|
-
" (
|
|
93
|
+
" (this endpoint refuses, because the reading would then describe a server you did not ask about)");
|
|
94
|
+
}
|
|
95
|
+
function declaredAsJudged(entry) {
|
|
96
|
+
const declared = entry;
|
|
97
|
+
if (!("toolAxes" in declared))
|
|
98
|
+
return declared;
|
|
99
|
+
const read = readMcpToolAxes(declared.toolAxes);
|
|
100
|
+
if (!read.ok)
|
|
101
|
+
return declared;
|
|
102
|
+
const { toolAxes: _declaredAxes, ...rest } = declared;
|
|
103
|
+
return read.toolAxes === undefined ? rest : { ...rest, toolAxes: read.toolAxes };
|
|
91
104
|
}
|
|
92
105
|
function stripEquivalentEmpties(entry) {
|
|
93
106
|
const emptyRecord = (v) => v !== null && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { REPO_PULL_DIFF_TOOL_NAME, REPO_READ_FILE_TOOL_NAME, REPO_TREE_TOOL_NAME } from "./host-tool-names.js";
|
|
3
4
|
export function parseRepo(input) {
|
|
4
5
|
const s = input.trim().replace(/\.git$/, "");
|
|
5
6
|
const m = /^[a-z]+:\/\/[^/]+\/(.+)$/i.exec(s);
|
|
@@ -160,7 +161,7 @@ export function repoToolsFor(client, coords) {
|
|
|
160
161
|
const ref = (r) => (r ? Promise.resolve(r) : client.defaultBranch(coords));
|
|
161
162
|
return [
|
|
162
163
|
{
|
|
163
|
-
name:
|
|
164
|
+
name: REPO_TREE_TOOL_NAME,
|
|
164
165
|
description: "List file paths in the repository (recursive). Use this first to understand structure, then read specific files.",
|
|
165
166
|
effect: "read",
|
|
166
167
|
parameters: Type.Object({ ref: Type.Optional(Type.String({ description: "branch/tag/sha; default = repo default branch" })) }),
|
|
@@ -171,7 +172,7 @@ export function repoToolsFor(client, coords) {
|
|
|
171
172
|
},
|
|
172
173
|
},
|
|
173
174
|
{
|
|
174
|
-
name:
|
|
175
|
+
name: REPO_READ_FILE_TOOL_NAME,
|
|
175
176
|
description: "Read one file's text from the repository. Read only the files you need (just-in-time).",
|
|
176
177
|
effect: "read",
|
|
177
178
|
parameters: Type.Object({ path: Type.String(), ref: Type.Optional(Type.String()) }),
|
|
@@ -181,7 +182,7 @@ export function repoToolsFor(client, coords) {
|
|
|
181
182
|
},
|
|
182
183
|
},
|
|
183
184
|
{
|
|
184
|
-
name:
|
|
185
|
+
name: REPO_PULL_DIFF_TOOL_NAME,
|
|
185
186
|
description: "Get the unified diff of a pull request by its number (for reviewing a PR's changes).",
|
|
186
187
|
effect: "read",
|
|
187
188
|
parameters: Type.Object({ index: Type.Integer({ description: "the PR number" }) }),
|
|
@@ -295,4 +295,38 @@ export declare function danglingScenarioRefusal(name: string, source: ScenarioNa
|
|
|
295
295
|
* ({@link danglingScenarioRefusal})。`principal` 只进那一行运维日志,不参与判定。
|
|
296
296
|
*/
|
|
297
297
|
export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger, principal?: string): string;
|
|
298
|
+
/**
|
|
299
|
+
* 一条请求的场景名过门 —— {@link gateScenarioRequest} 的**唯一一处实参装配**。
|
|
300
|
+
*
|
|
301
|
+
* 门的六个实参各有来源:中心裁定(`principalCaps.scenarioRuling`,按 principal)、请求点名的名字、部署缺省场景、**在册词表**
|
|
302
|
+
* (`Object.keys(scenarios)` —— 场景表活引用,不抄快照)、日志与 principal。「此主体在此场景能看见什么」这类问题有不止一个读口
|
|
303
|
+
* (任务提交腿 `resolveSpec`、`run-local`、`GET /v1/capabilities/mcp` 的状态面、`GET /v1/sessions/:id/mcp` / `…/a2a` 两张会话面板),
|
|
304
|
+
* 每个读口自己拼一遍这六个实参 = 第二只门:两口对「在册」「放行」「缺席取谁」各判各的,读口就开始对不上 run。
|
|
305
|
+
* 所以装配只在这里,读口持有的是**绑好的门**;凡回答这类问题的口,场景名都由门给 —— 点名与缺席都问。
|
|
306
|
+
*
|
|
307
|
+
* 契约:`(principal, requested) => 在册的场景名`(返回值一定在册;被拒 ⇒ 抛 {@link gateScenarioRequest} 的那几只 typed 错,
|
|
308
|
+
* 读口不改写)。`requested` 非串(缺席)⇒ 门走指派链(ruling 胜者,否则部署缺省),与 run 时同一读法。
|
|
309
|
+
* `principalCaps` 缺席(无中心 / 干跑 / 嵌入方 / `run-local`)⇒ 无 ruling,门退回「请求名或部署缺省」那条现状链。
|
|
310
|
+
*/
|
|
311
|
+
export interface ScenarioGateSources {
|
|
312
|
+
readonly config: {
|
|
313
|
+
readonly defaultScenario: string;
|
|
314
|
+
};
|
|
315
|
+
/** 场景表(活引用):词表按调用时的自有键取,与表同生同灭。 */
|
|
316
|
+
readonly scenarios: Readonly<Record<string, unknown>>;
|
|
317
|
+
readonly principalCaps: {
|
|
318
|
+
scenarioRuling(principal: string | undefined): Promise<ScenarioRuling | undefined>;
|
|
319
|
+
peekScenarioRuling(principal: string | undefined): Promise<ScenarioRuling | undefined>;
|
|
320
|
+
} | undefined;
|
|
321
|
+
readonly logger?: Logger;
|
|
322
|
+
}
|
|
323
|
+
export type ScenarioGate = (principal: string | undefined, requested: unknown) => Promise<string>;
|
|
324
|
+
/**
|
|
325
|
+
* 门问中心裁定走哪条腿(闭集;新增一词 ⇒ 下面的穷尽 `switch` 编译红)—— 与 `resolveRuntimeCaps` / `peekRuntimeCaps` 是同一对:
|
|
326
|
+
* · `enforce` —— 任务提交腿:中心裁定走执法腿,center 硬失败铸下的 fail-closed 降级项两腿都认;
|
|
327
|
+
* · `peek` —— 读面(`GET /v1/capabilities/mcp` 状态面、`GET /v1/sessions/:id/mcp` / `…/a2a` 两张会话面板):只读腿,同一只缓存 / 同一条抓取 / 同一份 fail-open 判决,但硬失败铸的降级项只对读面自己算数,
|
|
328
|
+
* 不替这个主体的真提交留下 `{allowWorkflows:false, allowFork:false, autoMode:false}` 的短窗 deny(读面不许成为别人被拒的原因)。
|
|
329
|
+
*/
|
|
330
|
+
export type ScenarioGateLeg = "enforce" | "peek";
|
|
331
|
+
export declare function createScenarioGate(sources: ScenarioGateSources, leg: ScenarioGateLeg): ScenarioGate;
|
|
298
332
|
//# sourceMappingURL=scenarios.d.ts.map
|
|
@@ -2,6 +2,7 @@ import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH
|
|
|
2
2
|
import { HttpError, createCodedHttpError } from "../security.js";
|
|
3
3
|
import { createSubagentExtraTools, extraToolsErrorFields, nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
5
|
+
import { NOW_TOOL_NAME, REPO_PULL_DIFF_TOOL_NAME, REPO_READ_FILE_TOOL_NAME, REPO_TREE_TOOL_NAME } from "./host-tool-names.js";
|
|
5
6
|
import { stablePrompt } from "./prompt.js";
|
|
6
7
|
import { createWebSearchBackend, judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
7
8
|
import { skillsForScenario } from "./skills.js";
|
|
@@ -119,16 +120,16 @@ export function builtinScenarioDetails(scenarios, deps) {
|
|
|
119
120
|
return fallback;
|
|
120
121
|
}
|
|
121
122
|
};
|
|
122
|
-
const REPO_TOOLS = [
|
|
123
|
+
const REPO_TOOLS = [REPO_TREE_TOOL_NAME, REPO_READ_FILE_TOOL_NAME, REPO_PULL_DIFF_TOOL_NAME, NOW_TOOL_NAME];
|
|
123
124
|
const mk = (name, summary, toolset, tools, promptSummary) => ({
|
|
124
125
|
name, source: "builtin", builtin: true, summary, toolset, tools, promptSummary, enabled: true,
|
|
125
126
|
...availabilityFields(deps, toolset),
|
|
126
127
|
});
|
|
127
128
|
const details = {
|
|
128
|
-
code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, [
|
|
129
|
+
code: mk("code", "编码主场景([891] 出厂缺省;CC 编码 persona 蒸馏版,default 全量工具面;终验旋钮独立)", "full-body", probe("code", {}, undefined, [NOW_TOOL_NAME]), deps.brandIdentity
|
|
129
130
|
? "Sema 产品身份 + core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"
|
|
130
131
|
: "core 编码完全体提示词(CODE_AGENT persona+自治自检;CC 血统对表)"),
|
|
131
|
-
default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, [
|
|
132
|
+
default: mk("default", "通用/助理场景(域中性 persona,CC agent loop 全量工具面;[891] 起非出厂缺省,显式可选)", "full-body", probe("default", {}, undefined, [NOW_TOOL_NAME]), deps.brandIdentity ? "Sema 产品身份 + core 默认宪法(结构层由引擎合成)" : "core 默认宪法(中性,无场景附加 persona)"),
|
|
132
133
|
"code-review": mk("code-review", "代码评审(clone-free 只读仓库工具)", "repo-readonly", probe("code-review", { repo: "probe/probe" }, undefined, REPO_TOOLS), "评审员提示词(产出契约)"),
|
|
133
134
|
scan: mk("scan", "中性只读仓库扫描(输出契约由 objective+skills 决定)", "repo-readonly", probe("scan", { repo: "probe/probe" }, undefined, REPO_TOOLS), "无场景附加提示词(objective+center skills 拥有输出契约)"),
|
|
134
135
|
};
|
|
@@ -149,7 +150,7 @@ export function centerScenarioDetails(specs, builtinNames, deps) {
|
|
|
149
150
|
builtin: builtinNames.includes(spec.name),
|
|
150
151
|
summary: "",
|
|
151
152
|
toolset: spec.toolset,
|
|
152
|
-
tools: spec.toolset === "repo-readonly" ? [
|
|
153
|
+
tools: spec.toolset === "repo-readonly" ? [REPO_TREE_TOOL_NAME, REPO_READ_FILE_TOOL_NAME, REPO_PULL_DIFF_TOOL_NAME, NOW_TOOL_NAME] : [NOW_TOOL_NAME],
|
|
153
154
|
promptSummary: "",
|
|
154
155
|
enabled: true,
|
|
155
156
|
...availabilityFields(deps, spec.toolset),
|
|
@@ -297,4 +298,19 @@ export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known
|
|
|
297
298
|
}
|
|
298
299
|
return name;
|
|
299
300
|
}
|
|
301
|
+
export function createScenarioGate(sources, leg) {
|
|
302
|
+
const rulingOf = (principal) => {
|
|
303
|
+
switch (leg) {
|
|
304
|
+
case "enforce":
|
|
305
|
+
return sources.principalCaps?.scenarioRuling(principal);
|
|
306
|
+
case "peek":
|
|
307
|
+
return sources.principalCaps?.peekScenarioRuling(principal);
|
|
308
|
+
default: {
|
|
309
|
+
const unhandled = leg;
|
|
310
|
+
return unhandled;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
return async (principal, requested) => gateScenarioRequest(await rulingOf(principal), requested, sources.config.defaultScenario, Object.keys(sources.scenarios), sources.logger, principal);
|
|
315
|
+
}
|
|
300
316
|
//# sourceMappingURL=scenarios.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
+
import { SELECT_ENVIRONMENT_TOOL_NAME } from "./host-tool-names.js";
|
|
2
3
|
import { BoundedSessionMap } from "../bounded-session-map.js";
|
|
3
4
|
import { resolveSandboxImageRef } from "../per-task-image.js";
|
|
4
5
|
export class SessionEnvironmentSelection {
|
|
@@ -22,7 +23,7 @@ async function availableProfiles(catalog, viewer) {
|
|
|
22
23
|
}
|
|
23
24
|
export function selectEnvironmentTool(deps) {
|
|
24
25
|
return {
|
|
25
|
-
name:
|
|
26
|
+
name: SELECT_ENVIRONMENT_TOOL_NAME,
|
|
26
27
|
description: "Switch this session's sandbox environment (image profile) or discover available ones. " +
|
|
27
28
|
"Call with no arguments to list available profiles and their capabilities. " +
|
|
28
29
|
"Call with `profile` (optionally `capabilitiesNeeded`) to select one. " +
|
|
@@ -43,7 +43,6 @@ export declare function sendUserFileTool(deps: {
|
|
|
43
43
|
}) => Promise<IssuedFileLink>;
|
|
44
44
|
emitter: SendUserFileEmitter;
|
|
45
45
|
}): ToolSpec;
|
|
46
|
-
export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
47
46
|
/**
|
|
48
47
|
* The deployment's group for the delegated-child injection (`createSubagentExtraTools` in `builtin-tools.ts` composes it with
|
|
49
48
|
* `Now` into the delegation tool's `extraTools` factory AND its `extraToolNames` roster — one list, read twice): SendUserFile,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
3
|
import { builtinAgentDefinitions } from "@sema-agent/core";
|
|
4
|
+
import { SEND_USER_FILE_TOOL_NAME } from "./host-tool-names.js";
|
|
4
5
|
export class SendUserFileEmitter {
|
|
5
6
|
als = new AsyncLocalStorage();
|
|
6
7
|
runWithContext(ctx, fn) {
|
|
@@ -19,7 +20,7 @@ export const SEND_USER_FILE_MAX_BYTES = 100 * 1024 * 1024;
|
|
|
19
20
|
export const SEND_USER_FILE_MAX_CAPTION = 500;
|
|
20
21
|
export function sendUserFileTool(deps) {
|
|
21
22
|
return {
|
|
22
|
-
name:
|
|
23
|
+
name: SEND_USER_FILE_TOOL_NAME,
|
|
23
24
|
description: "Send file(s) to the user — surfaced as downloadable/inline cards in their pane. " +
|
|
24
25
|
"Use when the file itself is the deliverable (generated charts, reports, screenshots, build artifacts) " +
|
|
25
26
|
"and you want it surfaced, not just mentioned. Files must already exist on local disk (this tool sends " +
|
|
@@ -91,7 +92,6 @@ export function sendUserFileTool(deps) {
|
|
|
91
92
|
},
|
|
92
93
|
};
|
|
93
94
|
}
|
|
94
|
-
export const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
95
95
|
const READONLY_BUILTIN_AGENT_TYPES = new Set(builtinAgentDefinitions().map((d) => d.name));
|
|
96
96
|
export function subagentSendUserFileExtraTools(deps) {
|
|
97
97
|
return { tools: [deps.spec], forSpawn: (ctx) => !(ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType)) };
|
|
@@ -107,7 +107,7 @@ export function skillsForScenario(loaded, scenario) {
|
|
|
107
107
|
return loaded.filter((k) => k.scenarios.length === 0 || k.scenarios.includes(scenario)).map((k) => k.spec);
|
|
108
108
|
}
|
|
109
109
|
export function parseFrontmatter(raw) {
|
|
110
|
-
const m = /^---\r?\n([\s\S]*?)\r?\n
|
|
110
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---[ \t]*\r?(?:\n|$)([\s\S]*)$/.exec(raw);
|
|
111
111
|
if (!m)
|
|
112
112
|
return { meta: {}, body: raw };
|
|
113
113
|
const meta = {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { SELECT_ENVIRONMENT_TOOL_NAME } from "./host-tool-names.js";
|
|
2
|
+
export const LONGTAIL_DEFER_TOOLS = new Set([SELECT_ENVIRONMENT_TOOL_NAME]);
|
|
2
3
|
export function applyLongtailDefer(tools, enabled) {
|
|
3
4
|
if (!enabled)
|
|
4
5
|
return tools;
|