@sema-agent/client-core 0.5.0 → 0.7.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/dist/adapt.d.ts +17 -3
- package/dist/adapt.js +555 -36
- package/dist/clientSlice.d.ts +169 -0
- package/dist/clientSlice.js +62 -0
- package/dist/diff/patch.d.ts +29 -0
- package/dist/diff/patch.js +45 -0
- package/dist/engineSessionParam.d.ts +7 -0
- package/dist/engineSessionParam.js +35 -0
- package/dist/engineWireSdk.d.ts +25 -1
- package/dist/engineWireSdk.js +30 -4
- package/dist/finalVerifyWire.d.ts +74 -0
- package/dist/finalVerifyWire.js +63 -0
- package/dist/headlessPermissionModeWire.d.ts +55 -0
- package/dist/headlessPermissionModeWire.js +111 -0
- package/dist/headlessReconnectWire.d.ts +96 -0
- package/dist/headlessReconnectWire.js +141 -0
- package/dist/host.d.ts +90 -0
- package/dist/host.js +84 -0
- package/dist/index.d.ts +36 -1
- package/dist/index.js +48 -3
- package/dist/interactiveToolsWire.d.ts +48 -0
- package/dist/interactiveToolsWire.js +86 -0
- package/dist/limitsWire.d.ts +89 -0
- package/dist/limitsWire.js +225 -0
- package/dist/notifications.d.ts +7 -0
- package/dist/notifications.js +32 -0
- package/dist/request/taskRequest.d.ts +135 -0
- package/dist/request/taskRequest.js +176 -0
- package/dist/sandboxWire.d.ts +75 -0
- package/dist/sandboxWire.js +138 -0
- package/dist/scenarioWire.d.ts +62 -0
- package/dist/scenarioWire.js +115 -0
- package/dist/scratchpadWireCaps.d.ts +16 -0
- package/dist/scratchpadWireCaps.js +65 -0
- package/dist/seam.d.ts +114 -11
- package/dist/seam.js +31 -0
- package/dist/toolResult.d.ts +118 -0
- package/dist/toolResult.js +774 -0
- package/package.json +4 -2
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* request/taskRequest.ts — **请求面合一**(B4,多端改造设计稿 §3 B4 第二件 + §8-5 裁决)。
|
|
3
|
+
*
|
|
4
|
+
* ## 它解决的是什么
|
|
5
|
+
*
|
|
6
|
+
* 搬迁前壳里有 **三个**独立的请求构造器,谁也不知道对方长什么样:
|
|
7
|
+
* · `S/seamQuery.toTaskRequest`(:535-869)—— 交互 REPL 车道;
|
|
8
|
+
* · `S/seamQueryEngine`(:546-578)—— headless `-p` 车道,**自己另拼一份**;
|
|
9
|
+
* · `S/liveClient.toLiveRequest`(:322-448)—— 两条车道共用的 live 兜底层。
|
|
10
|
+
*
|
|
11
|
+
* census §3.4 实测的漂移(B4 复核确认,逐字段见 `REQUEST_FIELD_MATRIX`):
|
|
12
|
+
* · **print 独有** `finalVerification` / `limits` / `interactiveTools`;
|
|
13
|
+
* · **TUI 独有** `ultracode` / `systemPrompt` / `reasoningEffort` / `images` / `model` /
|
|
14
|
+
* rewind 三件 / `clientContext` / `scratchpadDir` / 已解析 settings。
|
|
15
|
+
* 这不是「两条车道本来就该不同」——里面**一部分是有理由的,一部分纯粹是漏了**。合一的目的不是
|
|
16
|
+
* 把它们抹平(那是行为改动),而是把「谁有谁没有、为什么」变成**一张表**:再漏就会在 diff 里显形。
|
|
17
|
+
*
|
|
18
|
+
* ## 分工铁律
|
|
19
|
+
*
|
|
20
|
+
* 🔴 本模块**零 env、零全局状态、零 IO**——所有值由端解析好之后放进 `TaskRequestInput`。
|
|
21
|
+
* 壳读 `process.env` / `getAppState()` / `getSessionId()` / `process.argv`,web 读 BFF 下发的配置,
|
|
22
|
+
* 桌面读主进程。包只负责三件**端无关**的事:
|
|
23
|
+
* ① **字段集**:哪条车道 stamp 哪些字段(`REQUEST_FIELD_MATRIX`,带逐条理由);
|
|
24
|
+
* ② **合并语义**:`settings` 四源合并、objective 的一次性装饰序、sessionId 三态;
|
|
25
|
+
* ③ **live 门**:一个 `input.live` 布尔,替掉壳里散落的 20 处 `process.env.SEMA_LIVE_BASEURL` 判读。
|
|
26
|
+
*/
|
|
27
|
+
import type { TaskRequest } from '@sema-agent/sdk';
|
|
28
|
+
/** 请求车道(§8-5:print lane 收编进来,但 stderr 分诊文案与 PrintStreamProjector 留壳)。 */
|
|
29
|
+
export type RequestLane = 'interactive' | 'print';
|
|
30
|
+
/** `TaskRequest` 的可写投影(包内按结构操作;SDK 型只做出口约束)。 */
|
|
31
|
+
export type TaskRequestLike = Record<string, unknown>;
|
|
32
|
+
/**
|
|
33
|
+
* 字段矩阵的一条 —— **这就是「不许静默统一」的载体**。
|
|
34
|
+
*
|
|
35
|
+
* `lanes` 是**事实**(今天哪条车道 stamp 它),`why` 是**理由**(为什么另一条没有)。
|
|
36
|
+
* 两者分开写:`lanes` 变了会被下面的自洽断言逮住,`why` 让下一棒知道该不该补齐。
|
|
37
|
+
* `gap:true` = 这条差异**没有正当理由,是漏的**(候补齐;补齐是行为改动,要单独一条测试)。
|
|
38
|
+
*/
|
|
39
|
+
export interface RequestFieldSpec {
|
|
40
|
+
field: string;
|
|
41
|
+
lanes: readonly RequestLane[];
|
|
42
|
+
/** 仅在 live 车道 stamp(mock/pty fixture 车道请求形状恒不变)。 */
|
|
43
|
+
live: boolean;
|
|
44
|
+
why: string;
|
|
45
|
+
gap?: true;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 逐字段对照表(B4 从三个构造器**逐行**读出来的,不是设计出来的)。
|
|
49
|
+
*
|
|
50
|
+
* 读法:`lanes` 只有一条 = 今天的漂移面;`gap:true` = 我判它是**漏**而不是**设计**。
|
|
51
|
+
* 🔴 `gap` 是【判断】不是【事实】——补齐任何一条都要按宪法三问单独立项,别当 B4 的收尾顺手做。
|
|
52
|
+
*/
|
|
53
|
+
export declare const REQUEST_FIELD_MATRIX: readonly RequestFieldSpec[];
|
|
54
|
+
/** live 兜底层(`toLiveRequest`)追加的字段 —— 两条车道**都**经过,故不进上表。 */
|
|
55
|
+
export declare const LIVE_DEFAULT_FIELDS: readonly ["cwd", "additionalDirectories", "forwardSubagentEvents", "retainSubagentSessions", "agents", "appendSystemPrompt|settings.outputStyle", "suggestNextPrompts", "compactionModel", "sessionId(三态解析)"];
|
|
56
|
+
/** 端解析好的输入 —— 每一项都是**值**,不是取值方式(取值方式属端)。 */
|
|
57
|
+
export interface TaskRequestInput {
|
|
58
|
+
/** 本 turn 的模型面输入(已含 slash-skill 正文 / 注入式 meta / 历史种子等端侧组装)。 */
|
|
59
|
+
objective: string;
|
|
60
|
+
/** 壳会话 id(单命名空间);print 非 live 车道传 mock 常量。 */
|
|
61
|
+
sessionId: string;
|
|
62
|
+
/** live 车道(= 壳的 `process.env.SEMA_LIVE_BASEURL` 判真)。false ⇒ 全部 live-gated 字段不 stamp。 */
|
|
63
|
+
live: boolean;
|
|
64
|
+
scenario?: string;
|
|
65
|
+
systemPrompt?: string;
|
|
66
|
+
reasoningEffort?: string;
|
|
67
|
+
model?: string;
|
|
68
|
+
images?: readonly unknown[];
|
|
69
|
+
skills?: unknown;
|
|
70
|
+
mcpServers?: unknown;
|
|
71
|
+
agents?: unknown;
|
|
72
|
+
clientContext?: Record<string, unknown>;
|
|
73
|
+
scratchpadDir?: string;
|
|
74
|
+
sandboxImageProfile?: string;
|
|
75
|
+
permissionMode?: string;
|
|
76
|
+
selfOrchestration?: boolean;
|
|
77
|
+
deferTools?: readonly string[];
|
|
78
|
+
retainBackgroundProcesses?: boolean;
|
|
79
|
+
promptProfile?: string;
|
|
80
|
+
enableFork?: boolean;
|
|
81
|
+
attachments?: Record<string, unknown>;
|
|
82
|
+
finalVerification?: boolean;
|
|
83
|
+
limits?: Record<string, unknown>;
|
|
84
|
+
interactiveTools?: false;
|
|
85
|
+
/** rewind 三件(E18);端自己判空。 */
|
|
86
|
+
rewind?: Record<string, unknown>;
|
|
87
|
+
/** `settings` 的四个来源(端各自解析;本模块只负责合并序与「全空则不 stamp」)。 */
|
|
88
|
+
settings?: {
|
|
89
|
+
resolved?: Record<string, unknown>;
|
|
90
|
+
webSearch?: Record<string, unknown>;
|
|
91
|
+
ultracode?: boolean;
|
|
92
|
+
hooks?: Record<string, unknown>;
|
|
93
|
+
outputStyle?: string;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 合一后的请求构造器 —— **按车道出两形**,字段集差异全部由 `REQUEST_FIELD_MATRIX` 决定。
|
|
98
|
+
*
|
|
99
|
+
* 🔴 行为纪律:本函数**不做任何统一**。print 没有 `ultracode` 就是没有(表里 `gap:true` 记着账),
|
|
100
|
+
* 补齐要另立项 —— 在这里顺手加一行,就是把「合一」偷换成「行为改动」。
|
|
101
|
+
*/
|
|
102
|
+
export declare function buildTaskRequest(input: TaskRequestInput, lane: RequestLane): TaskRequestLike;
|
|
103
|
+
/** `applyLiveRequestDefaults` 的宿主输入(端解析好的值,同样零取值方式)。 */
|
|
104
|
+
export interface LiveDefaultsInput {
|
|
105
|
+
cwd?: string;
|
|
106
|
+
additionalDirectories?: readonly string[];
|
|
107
|
+
agents?: unknown;
|
|
108
|
+
/** 引擎 caps 判真 ⇒ 走 `appendSystemPrompt` 一等位;否则借道 `settings.outputStyle`。 */
|
|
109
|
+
appendSystemPromptCapable: boolean;
|
|
110
|
+
/** 自我认知块的合并结果(端把既有值传进来,包只决定落哪个位)。 */
|
|
111
|
+
selfKnowledge?: {
|
|
112
|
+
forAppend: string;
|
|
113
|
+
forOutputStyle: string;
|
|
114
|
+
};
|
|
115
|
+
/** E12 opt-in(缺省关 —— 开着 = 每个 completed run 后端多跑一次 LLM pass)。 */
|
|
116
|
+
suggestNextPrompts?: boolean;
|
|
117
|
+
/** 三重门都过之后的 cheap 槽模型名;端负责 caps + 配置 + 目录同步校验。 */
|
|
118
|
+
compactionModel?: string;
|
|
119
|
+
/** 前一 turn 捕获的引擎 session id(连续性最高优先)。 */
|
|
120
|
+
capturedSessionId?: string;
|
|
121
|
+
/** 端的 mock session 常量(命中 ⇒ 删 sessionId 让引擎新铸)。 */
|
|
122
|
+
mockSessionIdConstant?: string;
|
|
123
|
+
/** 需要从 scenario 位剔除的 mock 关键字。 */
|
|
124
|
+
mockScenarioKeys?: ReadonlySet<string>;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* live 兜底层(cli `liveClient.toLiveRequest` 的**包内半场**)—— 两条车道共用。
|
|
128
|
+
*
|
|
129
|
+
* 🔴 与 cli 的差分记账:cli 版在函数体里直接读 `process.cwd()` / `getAppStateStoreRef()` /
|
|
130
|
+
* `getLiveModelCatalog()` / `engineCapTrue()`。这些是**取值方式**,全部上移到 `LiveDefaultsInput`。
|
|
131
|
+
* 「调用方已带值就不覆盖」的语义(cli 每条都是 `if (out.X === undefined)`)在这里逐条保留。
|
|
132
|
+
*/
|
|
133
|
+
export declare function applyLiveRequestDefaults(req: TaskRequestLike, host: LiveDefaultsInput): TaskRequestLike;
|
|
134
|
+
/** 出口约束:构造结果确实是一个 `TaskRequest`(型只在这里碰 SDK,运行时零依赖)。 */
|
|
135
|
+
export type BuiltTaskRequest = TaskRequest;
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 逐字段对照表(B4 从三个构造器**逐行**读出来的,不是设计出来的)。
|
|
3
|
+
*
|
|
4
|
+
* 读法:`lanes` 只有一条 = 今天的漂移面;`gap:true` = 我判它是**漏**而不是**设计**。
|
|
5
|
+
* 🔴 `gap` 是【判断】不是【事实】——补齐任何一条都要按宪法三问单独立项,别当 B4 的收尾顺手做。
|
|
6
|
+
*/
|
|
7
|
+
export const REQUEST_FIELD_MATRIX = [
|
|
8
|
+
// ── 两条车道都有 ────────────────────────────────────────────────────────────────────────────
|
|
9
|
+
{ field: 'objective', lanes: ['interactive', 'print'], live: false, why: '本 turn 的真实输入,两条车道都必需' },
|
|
10
|
+
{ field: 'sessionId', lanes: ['interactive', 'print'], live: false, why: '单命名空间(TP-A Step 2):壳 id 即引擎 session id;print 在非 live 下保留 mock 常量' },
|
|
11
|
+
{ field: 'scenario', lanes: ['interactive', 'print'], live: false, why: 'TUI 走 selectScenario(objective) 嗅探,print 走 --scenario/SEMA_HEADLESS_SCENARIO 显式值' },
|
|
12
|
+
{ field: 'sandboxImageProfile', lanes: ['interactive', 'print'], live: true, why: '[686]② 显式 profile;auto 档只在 TUI 有(一次性 advisory 进 objective)' },
|
|
13
|
+
{ field: 'permissionMode', lanes: ['interactive', 'print'], live: true, why: 'TUI 读 AppState.toolPermissionContext(带 stampDefault 门),print 读 flag>env(headlessPermissionModeWire)' },
|
|
14
|
+
{ field: 'selfOrchestration', lanes: ['interactive', 'print'], live: true, why: 'SEMA_SELF_ORCHESTRATION opt-in → core 挂 run_workflow' },
|
|
15
|
+
{ field: 'deferTools', lanes: ['interactive', 'print'], live: true, why: '[1052]① Workflow 延迟披露;selfOrchestration 前置门' },
|
|
16
|
+
{ field: 'retainBackgroundProcesses', lanes: ['interactive', 'print'], live: true, why: '缺省 ON(clay 2026-07-26 翻转);SEMA_RETAIN_BACKGROUND=0 逃生' },
|
|
17
|
+
{ field: 'promptProfile', lanes: ['interactive', 'print'], live: true, why: 'core 1.328 呈现轴 A/B 口,缺省不 stamp = 引擎缺省 simple' },
|
|
18
|
+
{ field: 'enableFork', lanes: ['interactive', 'print'], live: true, why: '显式布尔才 stamp([503]② fail-OPEN 教训:off 必须显式)' },
|
|
19
|
+
{ field: 'attachments', lanes: ['interactive', 'print'], live: true, why: 'design/133 + G1 缺省开的 turn 边界 attachment' },
|
|
20
|
+
{ field: 'skills', lanes: ['interactive', 'print'], live: true, why: '本地 .claude/skills 投影;两条车道都由 launchRepl 载入面注入' },
|
|
21
|
+
{ field: 'mcpServers', lanes: ['interactive', 'print'], live: true, why: '本地 .mcp.json 投影;service 单用户门决定是否兑现' },
|
|
22
|
+
{ field: 'settings.webSearch', lanes: ['interactive', 'print'], live: true, why: 'SEMA_WEBSEARCH_* / settings.json;per-request 配置赢过部署 env' },
|
|
23
|
+
{ field: 'settings.hooks', lanes: ['interactive', 'print'], live: true, why: '[495]① 用户 settings 文件 hooks 逐字上 wire' },
|
|
24
|
+
// ── print 独有(§8-5 认定的**有理由**的差异)──────────────────────────────────────────────
|
|
25
|
+
{ field: 'finalVerification', lanes: ['print'], live: true, why: 'P1-1 终验:无人值守车道才需要引擎自证;交互 REPL 由人当场看结果。#106 裁 B(让位+告知)后交互面默认关' },
|
|
26
|
+
{ field: 'limits', lanes: ['print'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc' },
|
|
27
|
+
{ field: 'interactiveTools', lanes: ['print'], live: true, why: '[909]B 件3:无人值守 stamp false,从 roster 源头灭掉 AskUserQuestion/plan 门。交互车道 stamp false 等于自废武功' },
|
|
28
|
+
// ── TUI 独有 ────────────────────────────────────────────────────────────────────────────────
|
|
29
|
+
{ field: 'settings.ultracode', lanes: ['interactive'], live: true, why: 'design/111:sticky `/effort ultracode` 拨盘 + 当轮关键词嗅探,两个来源都只在交互面存在', gap: true },
|
|
30
|
+
{ field: 'systemPrompt', lanes: ['interactive'], live: false, why: 'CC QueryParams.systemPrompt;print 腿的 params 没有这一位' },
|
|
31
|
+
{ field: 'reasoningEffort', lanes: ['interactive'], live: false, why: '`/effort` 拨盘存在 AppState,print 无 AppState', gap: true },
|
|
32
|
+
{ field: 'model', lanes: ['interactive'], live: false, why: '`/model` 中途换模型读 toolUseContext.options.mainLoopModel;print 的模型走 MODEL_ID/--model 另一条路', gap: true },
|
|
33
|
+
{ field: 'images', lanes: ['interactive'], live: false, why: '贴图提交是交互动作,`-p` 的 stdin 没有图片块' },
|
|
34
|
+
{ field: 'resumeAt/rewindFiles/rewindFilesTo', lanes: ['interactive'], live: false, why: 'E18 `/rewind` 是交互命令' },
|
|
35
|
+
{ field: 'clientContext', lanes: ['interactive'], live: false, why: 'IANA 时区 + 可选邮箱 → core 本地化 `# Environment` 的 today;print 同样跑在用户机器上,缺席让引擎误标 (UTC)', gap: true },
|
|
36
|
+
{ field: 'scratchpadDir', lanes: ['interactive'], live: true, why: '[816]③ per-session 暂存目录;print 也有 session,缺席让 `-p` 的工具写不进 exemptDir', gap: true },
|
|
37
|
+
{ field: 'settings.<resolved>', lanes: ['interactive'], live: true, why: 'settings resolver 的 effective 快照(hooks/env/model + 已解析权限);print 只带 webSearch+hooks 两键', gap: true },
|
|
38
|
+
];
|
|
39
|
+
/** live 兜底层(`toLiveRequest`)追加的字段 —— 两条车道**都**经过,故不进上表。 */
|
|
40
|
+
export const LIVE_DEFAULT_FIELDS = [
|
|
41
|
+
'cwd',
|
|
42
|
+
'additionalDirectories',
|
|
43
|
+
'forwardSubagentEvents',
|
|
44
|
+
'retainSubagentSessions',
|
|
45
|
+
'agents',
|
|
46
|
+
'appendSystemPrompt|settings.outputStyle',
|
|
47
|
+
'suggestNextPrompts',
|
|
48
|
+
'compactionModel',
|
|
49
|
+
'sessionId(三态解析)',
|
|
50
|
+
];
|
|
51
|
+
const laneHas = (field, lane) => REQUEST_FIELD_MATRIX.find(f => f.field === field)?.lanes.includes(lane) ?? false;
|
|
52
|
+
/**
|
|
53
|
+
* 合一后的请求构造器 —— **按车道出两形**,字段集差异全部由 `REQUEST_FIELD_MATRIX` 决定。
|
|
54
|
+
*
|
|
55
|
+
* 🔴 行为纪律:本函数**不做任何统一**。print 没有 `ultracode` 就是没有(表里 `gap:true` 记着账),
|
|
56
|
+
* 补齐要另立项 —— 在这里顺手加一行,就是把「合一」偷换成「行为改动」。
|
|
57
|
+
*/
|
|
58
|
+
export function buildTaskRequest(input, lane) {
|
|
59
|
+
const live = input.live;
|
|
60
|
+
/** stamp 门:字段在本车道的表里 ∧(非 live-gated ∨ 本次是 live 车道)∧ 值非空。 */
|
|
61
|
+
const on = (field, value) => {
|
|
62
|
+
if (value === undefined || value === null)
|
|
63
|
+
return false;
|
|
64
|
+
const spec = REQUEST_FIELD_MATRIX.find(f => f.field === field);
|
|
65
|
+
if (spec === undefined)
|
|
66
|
+
return false;
|
|
67
|
+
if (!spec.lanes.includes(lane))
|
|
68
|
+
return false;
|
|
69
|
+
if (spec.live && !live)
|
|
70
|
+
return false;
|
|
71
|
+
return true;
|
|
72
|
+
};
|
|
73
|
+
// settings 四源合并(cli 原式:任一存在则 stamp,全缺则整个 `settings` 键都不出现)。
|
|
74
|
+
// ultracode 只在交互车道进表 ⇒ print 传了也不 stamp(表说了算,不是调用方说了算)。
|
|
75
|
+
const s = input.settings ?? {};
|
|
76
|
+
const settingsOut = {
|
|
77
|
+
...(laneHas('settings.<resolved>', lane) && live ? (s.resolved ?? {}) : {}),
|
|
78
|
+
...(on('settings.webSearch', s.webSearch) ? { webSearch: s.webSearch } : {}),
|
|
79
|
+
...(on('settings.ultracode', s.ultracode) ? { ultracode: s.ultracode } : {}),
|
|
80
|
+
...(on('settings.hooks', s.hooks) ? { hooks: s.hooks } : {}),
|
|
81
|
+
...(s.outputStyle !== undefined ? { outputStyle: s.outputStyle } : {}),
|
|
82
|
+
};
|
|
83
|
+
const out = {
|
|
84
|
+
objective: input.objective,
|
|
85
|
+
sessionId: input.sessionId,
|
|
86
|
+
...(on('scenario', input.scenario) ? { scenario: input.scenario } : {}),
|
|
87
|
+
...(on('systemPrompt', input.systemPrompt) ? { systemPrompt: input.systemPrompt } : {}),
|
|
88
|
+
...(on('reasoningEffort', input.reasoningEffort) ? { reasoningEffort: input.reasoningEffort } : {}),
|
|
89
|
+
...(on('model', input.model) ? { model: input.model } : {}),
|
|
90
|
+
...(on('images', input.images) && (input.images?.length ?? 0) > 0 ? { images: input.images } : {}),
|
|
91
|
+
...(on('skills', input.skills) ? { skills: input.skills } : {}),
|
|
92
|
+
...(on('mcpServers', input.mcpServers) ? { mcpServers: input.mcpServers } : {}),
|
|
93
|
+
...(on('resumeAt/rewindFiles/rewindFilesTo', input.rewind) ? input.rewind : {}),
|
|
94
|
+
...(on('permissionMode', input.permissionMode) ? { permissionMode: input.permissionMode } : {}),
|
|
95
|
+
...(on('sandboxImageProfile', input.sandboxImageProfile)
|
|
96
|
+
? { sandboxImageProfile: input.sandboxImageProfile }
|
|
97
|
+
: {}),
|
|
98
|
+
...(on('selfOrchestration', input.selfOrchestration) ? { selfOrchestration: input.selfOrchestration } : {}),
|
|
99
|
+
...(on('deferTools', input.deferTools) ? { deferTools: input.deferTools } : {}),
|
|
100
|
+
...(on('retainBackgroundProcesses', input.retainBackgroundProcesses)
|
|
101
|
+
? { retainBackgroundProcesses: input.retainBackgroundProcesses }
|
|
102
|
+
: {}),
|
|
103
|
+
...(on('promptProfile', input.promptProfile) ? { promptProfile: input.promptProfile } : {}),
|
|
104
|
+
...(on('enableFork', input.enableFork) ? { enableFork: input.enableFork } : {}),
|
|
105
|
+
...(on('attachments', input.attachments) ? { attachments: input.attachments } : {}),
|
|
106
|
+
...(on('clientContext', input.clientContext) ? { clientContext: input.clientContext } : {}),
|
|
107
|
+
...(on('scratchpadDir', input.scratchpadDir) ? { scratchpadDir: input.scratchpadDir } : {}),
|
|
108
|
+
...(on('finalVerification', input.finalVerification)
|
|
109
|
+
? { finalVerification: input.finalVerification }
|
|
110
|
+
: {}),
|
|
111
|
+
...(on('limits', input.limits) ? { limits: input.limits } : {}),
|
|
112
|
+
...(on('interactiveTools', input.interactiveTools) && input.interactiveTools === false
|
|
113
|
+
? { interactiveTools: false }
|
|
114
|
+
: {}),
|
|
115
|
+
...(Object.keys(settingsOut).length > 0 ? { settings: settingsOut } : {}),
|
|
116
|
+
};
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* live 兜底层(cli `liveClient.toLiveRequest` 的**包内半场**)—— 两条车道共用。
|
|
121
|
+
*
|
|
122
|
+
* 🔴 与 cli 的差分记账:cli 版在函数体里直接读 `process.cwd()` / `getAppStateStoreRef()` /
|
|
123
|
+
* `getLiveModelCatalog()` / `engineCapTrue()`。这些是**取值方式**,全部上移到 `LiveDefaultsInput`。
|
|
124
|
+
* 「调用方已带值就不覆盖」的语义(cli 每条都是 `if (out.X === undefined)`)在这里逐条保留。
|
|
125
|
+
*/
|
|
126
|
+
export function applyLiveRequestDefaults(req, host) {
|
|
127
|
+
const out = { ...req };
|
|
128
|
+
if (out.cwd == null && host.cwd !== undefined)
|
|
129
|
+
out.cwd = host.cwd;
|
|
130
|
+
if (out.additionalDirectories === undefined && (host.additionalDirectories?.length ?? 0) > 0) {
|
|
131
|
+
out.additionalDirectories = [...(host.additionalDirectories ?? [])];
|
|
132
|
+
}
|
|
133
|
+
// C1 / design/144:交互与 headless 都常开 —— 不开则引擎不建 SubagentRetainLedger,
|
|
134
|
+
// 后台子代 SendMessage 唤醒直接 "session was not retained" 拒绝。老引擎按 body→spec 白名单
|
|
135
|
+
// 忽略未知字段,version-safe。
|
|
136
|
+
if (out.forwardSubagentEvents === undefined)
|
|
137
|
+
out.forwardSubagentEvents = true;
|
|
138
|
+
if (out.retainSubagentSessions === undefined)
|
|
139
|
+
out.retainSubagentSessions = true;
|
|
140
|
+
if (out.agents === undefined && host.agents !== undefined)
|
|
141
|
+
out.agents = host.agents;
|
|
142
|
+
if (typeof out.scenario === 'string' && host.mockScenarioKeys?.has(out.scenario)) {
|
|
143
|
+
delete out.scenario;
|
|
144
|
+
}
|
|
145
|
+
// 自我认知块双臂([1487]/[1478] R1 终形):caps 判真走一等位,否则借道 outputStyle。
|
|
146
|
+
// 两臂同一合并序(知识块恒前、调用方块恒后)——序由端在 selfKnowledge 里拼好,包只选位。
|
|
147
|
+
if (host.selfKnowledge !== undefined) {
|
|
148
|
+
if (host.appendSystemPromptCapable) {
|
|
149
|
+
out.appendSystemPrompt = host.selfKnowledge.forAppend;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
const settings = (out.settings ?? {});
|
|
153
|
+
out.settings = { ...settings, outputStyle: host.selfKnowledge.forOutputStyle };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// E12:与 verify/cascade 互斥(那两个返 result 非 streamed run ⇒ 400)。
|
|
157
|
+
if (out.suggestNextPrompts === undefined &&
|
|
158
|
+
out.verify === undefined &&
|
|
159
|
+
out.cascade === undefined &&
|
|
160
|
+
host.suggestNextPrompts === true) {
|
|
161
|
+
out.suggestNextPrompts = true;
|
|
162
|
+
}
|
|
163
|
+
if (out.compactionModel === undefined && host.compactionModel !== undefined) {
|
|
164
|
+
out.compactionModel = host.compactionModel;
|
|
165
|
+
}
|
|
166
|
+
// 单命名空间三态(cli 逐字):① 已捕获的引擎 session 赢(连续性 —— 引擎 session 绝不在
|
|
167
|
+
// 会话中途被换掉);③ 还带着 mock 常量 ⇒ 删掉让引擎新铸;② 其余保留(首 turn 壳 id 直传)。
|
|
168
|
+
if (host.capturedSessionId) {
|
|
169
|
+
out.sessionId = host.capturedSessionId;
|
|
170
|
+
}
|
|
171
|
+
else if (host.mockSessionIdConstant !== undefined &&
|
|
172
|
+
out.sessionId === host.mockSessionIdConstant) {
|
|
173
|
+
delete out.sessionId;
|
|
174
|
+
}
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sema/sandboxWire.ts — [686]② `--sandbox` TRI-STATE flag (clay 拍, 2026-07-13): the CLI face of the
|
|
3
|
+
* per-task SANDBOX IMAGE PROFILE the server already ships end-to-end (`TaskRequest.sandboxImageProfile`
|
|
4
|
+
* per-task binding + the model-facing `SelectEnvironment` tool + the `GET /v1/images` catalog).
|
|
5
|
+
*
|
|
6
|
+
* TRI-STATE (clay 口径):
|
|
7
|
+
* a) DEFAULT (no flag) → NO `sandboxImageProfile` on the POST body — the worker's default image runs.
|
|
8
|
+
* b) `--sandbox <profile>` → EXPLICIT: stamp `sandboxImageProfile` (SDK types.ts:115 → service
|
|
9
|
+
* main.ts:2316 resolves profile→digest FAIL-CLOSED with the caller's principal — we only ever send the
|
|
10
|
+
* PROFILE, never a digest). Vocabulary = the live catalog (`sema cloud images list` / GET /v1/images) —
|
|
11
|
+
* the shell does NOT pre-validate against it (fail-closed admission is the server's job; a typo comes
|
|
12
|
+
* back as an honest 404, mapped to a hint by sandboxDegradeHint below).
|
|
13
|
+
* c) `--sandbox auto` → ADVISORY: the submit carries NO profile; instead the FIRST live turn's
|
|
14
|
+
* objective is prefixed with an advisory block asking the model to drive the `SelectEnvironment` tool
|
|
15
|
+
* itself (list → pick → report `Sandbox: <profile> — <reason>` in its reply, so the choice + reason are
|
|
16
|
+
* visible on the run card and correctable by the user). NOT a hard route — server keeps zero new
|
|
17
|
+
* endpoints; the tool's session binding takes effect from the NEXT task boundary ([440] F3 contract).
|
|
18
|
+
*
|
|
19
|
+
* LANE HONESTY (诚实不撒谎 UI, [686] constraint): the profile only works on the k8s sandbox lane — an
|
|
20
|
+
* e2b/host-lane worker (including the local TOC self-spawned engine) 400s the submit honestly
|
|
21
|
+
* (service main.ts:2323 "sandboxImageProfile is only supported on the k8s sandbox backend"). The shell does
|
|
22
|
+
* NOT pre-judge the lane (no caps sniffing before submit — per [686]: "不预判 lane,收到 400 再提示");
|
|
23
|
+
* it maps the server's honest reject into a degradation hint (sandboxDegradeHint) rendered on the error row.
|
|
24
|
+
* So on the local self-spawned engine `--sandbox <profile>` = a LOUD per-turn failure with an actionable
|
|
25
|
+
* hint — never a silent ignore (silently dropping the user's explicit environment choice would be lying).
|
|
26
|
+
*/
|
|
27
|
+
export type SandboxSelection = {
|
|
28
|
+
mode: 'default';
|
|
29
|
+
} | {
|
|
30
|
+
mode: 'auto';
|
|
31
|
+
} | {
|
|
32
|
+
mode: 'explicit';
|
|
33
|
+
profile: string;
|
|
34
|
+
};
|
|
35
|
+
export type SandboxParseResult = {
|
|
36
|
+
ok: true;
|
|
37
|
+
sel: SandboxSelection;
|
|
38
|
+
} | {
|
|
39
|
+
ok: false;
|
|
40
|
+
error: string;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Parse `--sandbox` out of an argv slice (everything before a bare `--`; both `--sandbox <v>` and
|
|
44
|
+
* `--sandbox=<v>` forms). No flag ⇒ `{mode:'default'}`. Repeated flag ⇒ LAST wins (commander convention).
|
|
45
|
+
* Missing value / flag-shaped value / empty / over-long ⇒ fail-LOUD parse error (never a silent default —
|
|
46
|
+
* a mis-typed environment choice must not quietly run on the wrong image).
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseSandboxArgv(argv: string[]): SandboxParseResult;
|
|
49
|
+
/** The request-body stamp for a selection: EXPLICIT ⇒ `{sandboxImageProfile}`, default/auto ⇒ nothing
|
|
50
|
+
* (auto is advisory-by-prompt — the submit itself never carries a profile, per clay's c) 口径). */
|
|
51
|
+
export declare function sandboxRequestFields(sel: SandboxSelection | undefined): {
|
|
52
|
+
sandboxImageProfile?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* The `--sandbox auto` advisory block. Design choices (each deliberate):
|
|
56
|
+
* - Conditional on the tool being mounted: `SelectEnvironment` reaches the model via the deployment's
|
|
57
|
+
* `spec.tools` injection (service select-environment-tool.ts RFC A2) — a worker without it must degrade
|
|
58
|
+
* HONESTLY (model says so and continues on the default), not hallucinate a selection.
|
|
59
|
+
* - The `Sandbox: <profile> — <reason>` reply line IS the run-card echo (可观测可纠正): the tool call
|
|
60
|
+
* renders as a normal tool card and the choice+reason line lands in the assistant reply — zero new
|
|
61
|
+
* renderer machinery, zero new server endpoints.
|
|
62
|
+
* - Boundary honesty: the server applies a session's selection from the NEXT task boundary ([440] F3),
|
|
63
|
+
* so the advisory says so — the model must not promise the current turn already runs on the new image.
|
|
64
|
+
*/
|
|
65
|
+
export declare const AUTO_SANDBOX_ADVISORY: string;
|
|
66
|
+
/** Prefix an objective with the auto-select advisory (one-shot — the caller owns the latch). */
|
|
67
|
+
export declare function wrapObjectiveWithAutoSandboxAdvisory(objective: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Map a submit failure → the sandbox DEGRADATION HINT, or undefined when the error is not sandbox-shaped.
|
|
70
|
+
* Keys on the server's honest reject texts (ai-agent-service main.ts:2323-2330, per-task-image.ts:52,
|
|
71
|
+
* server.ts:4651/4670) — status+substring, the same recognize-by-wire-shape idiom the quota/scenario
|
|
72
|
+
* copy modules use. The caller renders it under the raw "API Error: …" row (original message preserved —
|
|
73
|
+
* the hint supplements, never masks, the server's own words).
|
|
74
|
+
*/
|
|
75
|
+
export declare function sandboxDegradeHint(err: unknown): string | undefined;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sema/sandboxWire.ts — [686]② `--sandbox` TRI-STATE flag (clay 拍, 2026-07-13): the CLI face of the
|
|
3
|
+
* per-task SANDBOX IMAGE PROFILE the server already ships end-to-end (`TaskRequest.sandboxImageProfile`
|
|
4
|
+
* per-task binding + the model-facing `SelectEnvironment` tool + the `GET /v1/images` catalog).
|
|
5
|
+
*
|
|
6
|
+
* TRI-STATE (clay 口径):
|
|
7
|
+
* a) DEFAULT (no flag) → NO `sandboxImageProfile` on the POST body — the worker's default image runs.
|
|
8
|
+
* b) `--sandbox <profile>` → EXPLICIT: stamp `sandboxImageProfile` (SDK types.ts:115 → service
|
|
9
|
+
* main.ts:2316 resolves profile→digest FAIL-CLOSED with the caller's principal — we only ever send the
|
|
10
|
+
* PROFILE, never a digest). Vocabulary = the live catalog (`sema cloud images list` / GET /v1/images) —
|
|
11
|
+
* the shell does NOT pre-validate against it (fail-closed admission is the server's job; a typo comes
|
|
12
|
+
* back as an honest 404, mapped to a hint by sandboxDegradeHint below).
|
|
13
|
+
* c) `--sandbox auto` → ADVISORY: the submit carries NO profile; instead the FIRST live turn's
|
|
14
|
+
* objective is prefixed with an advisory block asking the model to drive the `SelectEnvironment` tool
|
|
15
|
+
* itself (list → pick → report `Sandbox: <profile> — <reason>` in its reply, so the choice + reason are
|
|
16
|
+
* visible on the run card and correctable by the user). NOT a hard route — server keeps zero new
|
|
17
|
+
* endpoints; the tool's session binding takes effect from the NEXT task boundary ([440] F3 contract).
|
|
18
|
+
*
|
|
19
|
+
* LANE HONESTY (诚实不撒谎 UI, [686] constraint): the profile only works on the k8s sandbox lane — an
|
|
20
|
+
* e2b/host-lane worker (including the local TOC self-spawned engine) 400s the submit honestly
|
|
21
|
+
* (service main.ts:2323 "sandboxImageProfile is only supported on the k8s sandbox backend"). The shell does
|
|
22
|
+
* NOT pre-judge the lane (no caps sniffing before submit — per [686]: "不预判 lane,收到 400 再提示");
|
|
23
|
+
* it maps the server's honest reject into a degradation hint (sandboxDegradeHint) rendered on the error row.
|
|
24
|
+
* So on the local self-spawned engine `--sandbox <profile>` = a LOUD per-turn failure with an actionable
|
|
25
|
+
* hint — never a silent ignore (silently dropping the user's explicit environment choice would be lying).
|
|
26
|
+
*/
|
|
27
|
+
/** Mirror of the server's own body validation (server.ts:4651): non-empty, ≤128 chars. Validating locally
|
|
28
|
+
* just converts a guaranteed 400 into an immediate CLI error — same rule, earlier and cheaper. */
|
|
29
|
+
const MAX_PROFILE_LEN = 128;
|
|
30
|
+
const USAGE_HINT = "sema: --sandbox expects a profile name or 'auto'.\n" +
|
|
31
|
+
' --sandbox <profile> run on that sandbox image profile (k8s-lane workers; list: sema cloud images list)\n' +
|
|
32
|
+
' --sandbox auto let the model pick via its SelectEnvironment tool (advisory — choice + reason shown in the reply)\n' +
|
|
33
|
+
' (no flag) the worker default image';
|
|
34
|
+
/**
|
|
35
|
+
* Parse `--sandbox` out of an argv slice (everything before a bare `--`; both `--sandbox <v>` and
|
|
36
|
+
* `--sandbox=<v>` forms). No flag ⇒ `{mode:'default'}`. Repeated flag ⇒ LAST wins (commander convention).
|
|
37
|
+
* Missing value / flag-shaped value / empty / over-long ⇒ fail-LOUD parse error (never a silent default —
|
|
38
|
+
* a mis-typed environment choice must not quietly run on the wrong image).
|
|
39
|
+
*/
|
|
40
|
+
export function parseSandboxArgv(argv) {
|
|
41
|
+
let sel = { mode: 'default' };
|
|
42
|
+
for (let i = 0; i < argv.length; i++) {
|
|
43
|
+
const a = argv[i];
|
|
44
|
+
if (a === '--')
|
|
45
|
+
break; // positionals — never flags
|
|
46
|
+
let raw;
|
|
47
|
+
if (a === '--sandbox') {
|
|
48
|
+
const nxt = argv[i + 1];
|
|
49
|
+
if (nxt === undefined || nxt.startsWith('-')) {
|
|
50
|
+
return { ok: false, error: USAGE_HINT };
|
|
51
|
+
}
|
|
52
|
+
raw = nxt;
|
|
53
|
+
i++; // consume the value token
|
|
54
|
+
}
|
|
55
|
+
else if (a.startsWith('--sandbox=')) {
|
|
56
|
+
raw = a.slice('--sandbox='.length);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const v = raw.trim();
|
|
62
|
+
if (v.length === 0)
|
|
63
|
+
return { ok: false, error: USAGE_HINT };
|
|
64
|
+
// audit #17: a bare `--sandbox` EATS the following positional — `-p --sandbox 'say hi'`
|
|
65
|
+
// consumed the prompt as the profile, then died on an unrelated "Input must be provided…"
|
|
66
|
+
// error. Profile names never contain whitespace; a value that does is almost certainly the
|
|
67
|
+
// user's prompt — say exactly that instead of silently mis-parsing.
|
|
68
|
+
if (/\s/.test(v)) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
error: `Invalid sandbox profile "${v}". Profile names cannot contain spaces — if you meant it as the prompt, ` +
|
|
72
|
+
`pass --sandbox a profile name (or 'auto') first, or omit --sandbox to use the worker default image.\n` +
|
|
73
|
+
USAGE_HINT,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (v.length > MAX_PROFILE_LEN) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: `sema: --sandbox profile must be at most ${MAX_PROFILE_LEN} characters (same rule the server enforces).`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
sel = v === 'auto' ? { mode: 'auto' } : { mode: 'explicit', profile: v };
|
|
83
|
+
}
|
|
84
|
+
return { ok: true, sel };
|
|
85
|
+
}
|
|
86
|
+
/** The request-body stamp for a selection: EXPLICIT ⇒ `{sandboxImageProfile}`, default/auto ⇒ nothing
|
|
87
|
+
* (auto is advisory-by-prompt — the submit itself never carries a profile, per clay's c) 口径). */
|
|
88
|
+
export function sandboxRequestFields(sel) {
|
|
89
|
+
return sel?.mode === 'explicit' ? { sandboxImageProfile: sel.profile } : {};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The `--sandbox auto` advisory block. Design choices (each deliberate):
|
|
93
|
+
* - Conditional on the tool being mounted: `SelectEnvironment` reaches the model via the deployment's
|
|
94
|
+
* `spec.tools` injection (service select-environment-tool.ts RFC A2) — a worker without it must degrade
|
|
95
|
+
* HONESTLY (model says so and continues on the default), not hallucinate a selection.
|
|
96
|
+
* - The `Sandbox: <profile> — <reason>` reply line IS the run-card echo (可观测可纠正): the tool call
|
|
97
|
+
* renders as a normal tool card and the choice+reason line lands in the assistant reply — zero new
|
|
98
|
+
* renderer machinery, zero new server endpoints.
|
|
99
|
+
* - Boundary honesty: the server applies a session's selection from the NEXT task boundary ([440] F3),
|
|
100
|
+
* so the advisory says so — the model must not promise the current turn already runs on the new image.
|
|
101
|
+
*/
|
|
102
|
+
export const AUTO_SANDBOX_ADVISORY = '<sandbox-auto-select>\n' +
|
|
103
|
+
'The user launched this session with `--sandbox auto`: choose the best sandbox environment (image profile) for the work in this session yourself.\n' +
|
|
104
|
+
'If a SelectEnvironment tool is available: call it with no arguments to list the available profiles and their capabilities, then select the one that best fits the task (keep the default if nothing fits better). After deciding, report your choice on its own line, exactly in this form: `Sandbox: <profile> — <one-line reason>` (use `Sandbox: default — <reason>` if you kept the default). Note the selection takes effect from the next task boundary — the current turn keeps the environment it started with.\n' +
|
|
105
|
+
'If no SelectEnvironment tool is available, briefly say so and continue in the default environment.\n' +
|
|
106
|
+
'</sandbox-auto-select>';
|
|
107
|
+
/** Prefix an objective with the auto-select advisory (one-shot — the caller owns the latch). */
|
|
108
|
+
export function wrapObjectiveWithAutoSandboxAdvisory(objective) {
|
|
109
|
+
return `${AUTO_SANDBOX_ADVISORY}\n\n${objective}`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Map a submit failure → the sandbox DEGRADATION HINT, or undefined when the error is not sandbox-shaped.
|
|
113
|
+
* Keys on the server's honest reject texts (ai-agent-service main.ts:2323-2330, per-task-image.ts:52,
|
|
114
|
+
* server.ts:4651/4670) — status+substring, the same recognize-by-wire-shape idiom the quota/scenario
|
|
115
|
+
* copy modules use. The caller renders it under the raw "API Error: …" row (original message preserved —
|
|
116
|
+
* the hint supplements, never masks, the server's own words).
|
|
117
|
+
*/
|
|
118
|
+
export function sandboxDegradeHint(err) {
|
|
119
|
+
const e = err;
|
|
120
|
+
if (typeof e?.status !== 'number' || typeof e?.message !== 'string')
|
|
121
|
+
return undefined;
|
|
122
|
+
const msg = e.message;
|
|
123
|
+
if (e.status === 400 && msg.includes('only supported on the k8s sandbox backend')) {
|
|
124
|
+
return ('--sandbox <profile> only applies on k8s-lane workers; this engine runs a host/e2b sandbox lane, so the request was honestly rejected.\n' +
|
|
125
|
+
'Re-run without --sandbox (worker default image), or point the shell at a k8s-lane worker (sema cloud engine connect <url>).');
|
|
126
|
+
}
|
|
127
|
+
if (e.status === 400 && msg.includes('requires the image index')) {
|
|
128
|
+
return ('This worker has no sandbox image index configured (sema-registry backend absent), so per-task profiles cannot resolve.\n' +
|
|
129
|
+
'Re-run without --sandbox, or use a worker with the image index enabled.');
|
|
130
|
+
}
|
|
131
|
+
if (e.status === 404 && msg.includes('no published sandbox image for profile')) {
|
|
132
|
+
return 'No published image matches that profile for your principal. List what you can use: sema cloud images list';
|
|
133
|
+
}
|
|
134
|
+
if (e.status === 400 && msg.includes('not supported together with cascade/verify')) {
|
|
135
|
+
return '--sandbox cannot combine with cascade/verify submits in v1 (the per-task image would not bind to the sub-runs). Drop one of the two.';
|
|
136
|
+
}
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sema/scenarioWire.ts — `--scenario <name>` wire (clay 拍, 2026-07-15): expose the engine's
|
|
3
|
+
* REQUEST-LEVEL scenario routing axis on the headless `-p` path. The server has shipped scenario
|
|
4
|
+
* routing since day one (ai-agent-service capabilities/scenarios.ts — `TaskRequest.scenario` picks the
|
|
5
|
+
* tool roster + prompt provider per request: default / code-review / scan / oa / team + center-declared
|
|
6
|
+
* overlays); the shell simply never exposed the field to users. This module is the CLI face.
|
|
7
|
+
*
|
|
8
|
+
* PRECEDENCE (flag > settings > default):
|
|
9
|
+
* a) `--scenario <name>` (explicit flag) → stamp `TaskRequest.scenario`.
|
|
10
|
+
* b) `SEMA_HEADLESS_SCENARIO` env — the settings lane: → stamp when no flag. Set it in the user
|
|
11
|
+
* settings.json `env` block (seeded into process.env by the SAME 1a-envseed both the REPL boot and
|
|
12
|
+
* printModeEngine run), the lane every other wire config rides (SEMA_SELF_ORCHESTRATION,
|
|
13
|
+
* SEMA_WEBSEARCH_*, SEMA_ENABLE_FORK). One mechanism serves both "env var" and "settings file".
|
|
14
|
+
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
15
|
+
*
|
|
16
|
+
* VOCABULARY = the server's scenario registry (builtin + center-declared). The shell does NOT
|
|
17
|
+
* pre-validate against it (the registry is deployment-side truth): an UNKNOWN name falls back to the
|
|
18
|
+
* `default` scenario server-side (scenarios.ts selectScenario — safe), and a center allowlist violation
|
|
19
|
+
* comes back as an honest 400 `scenario_not_allowed` (already rendered by scenarioNotAllowedCopy.ts).
|
|
20
|
+
* We only mirror the server's NAME-SHAPE rule (scenarios.ts:318 SCENARIO_NAME_RE) locally — same rule,
|
|
21
|
+
* earlier and cheaper, and it keeps prompt-looking values (whitespace etc.) from being eaten as a name.
|
|
22
|
+
*
|
|
23
|
+
* NOTE (mock-key shadow): the seam also uses `scenario` as the MOCK transcript selector
|
|
24
|
+
* (seamQuery.selectScenario / SEMA_SCENARIO). liveClient strips those mock keys before a live forward;
|
|
25
|
+
* a `--scenario` value that collides with a mock key (e.g. `bash`) is therefore equivalent to `default`
|
|
26
|
+
* on the live engine — which is exactly what the server would resolve for it anyway (no such scenario).
|
|
27
|
+
*
|
|
28
|
+
* SEMA_SCENARIO (the mock harness opt-in — "never spawn a real engine", printModeEngine.ts) is
|
|
29
|
+
* deliberately NOT reused here: it selects offline mock transcripts, not live engine routing.
|
|
30
|
+
*/
|
|
31
|
+
import { type EnvLike } from './hostEnv.js';
|
|
32
|
+
/** Mirror of the server's scenario NAME shape (ai-agent-service scenarios.ts:318). */
|
|
33
|
+
export declare const SCENARIO_NAME_RE: RegExp;
|
|
34
|
+
/** Settings-lane env knob (settings.json `env` block → 1a-envseed → process.env). */
|
|
35
|
+
export declare const HEADLESS_SCENARIO_ENV = "SEMA_HEADLESS_SCENARIO";
|
|
36
|
+
export type ScenarioParseResult = {
|
|
37
|
+
ok: true;
|
|
38
|
+
scenario?: string;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Parse `--scenario` out of an argv slice (everything before a bare `--`; both `--scenario <v>` and
|
|
45
|
+
* `--scenario=<v>` forms — the sandboxWire idiom). No flag ⇒ `{ok:true}` with no scenario. Repeated
|
|
46
|
+
* flag ⇒ LAST wins (commander convention). Missing value / flag-shaped value / name-shape violation ⇒
|
|
47
|
+
* fail-LOUD parse error (never a silent default — a mis-typed routing choice must not quietly run on
|
|
48
|
+
* the wrong tool surface; same stance as `--sandbox`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseScenarioArgv(argv: string[]): ScenarioParseResult;
|
|
51
|
+
/**
|
|
52
|
+
* The settings-lane default (`SEMA_HEADLESS_SCENARIO`). FAIL-SOFT on a bad value (stderr warning +
|
|
53
|
+
* no stamp): a settings-file typo must not hard-brick every headless run the way an explicit flag
|
|
54
|
+
* typo fails the one run it was typed on.
|
|
55
|
+
*/
|
|
56
|
+
export declare function headlessScenarioFromEnv(env?: EnvLike): string | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Resolve the scenario for a headless `-p` submit: explicit `--scenario` flag > `SEMA_HEADLESS_SCENARIO`
|
|
59
|
+
* settings default > none (= the engine's default scenario, today's behaviour). Flag parse errors
|
|
60
|
+
* propagate fail-loud; the caller exits.
|
|
61
|
+
*/
|
|
62
|
+
export declare function scenarioForPrint(argv: string[], env?: EnvLike): ScenarioParseResult;
|