@x-otto/prompt 0.0.1-alpha.4 → 0.1.0-alpha.10
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/README.md +2 -5
- package/dist/index.d.ts +28 -52
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -14
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/prompts/lead-guidance.md +7 -4
- package/prompts/skill-loop-guidance.md +9 -0
package/README.md
CHANGED
|
@@ -13,7 +13,6 @@ Manages the loading, assembly, and capability gating of system prompt templates.
|
|
|
13
13
|
| HttpPromptProvider | HTTP remote template loading (a swappable provider, not currently used in production) |
|
|
14
14
|
| sub-agent-prompt | AgentProfile type + subagent prompt assembly + profile resolution |
|
|
15
15
|
| tool-gated-sections | Gates sections marked `requires-capability` by capability key |
|
|
16
|
-
| environment-context | Workspace + Git + OS info collection and formatting |
|
|
17
16
|
| lesson-injection | Formats and injects learned experience (lessons) |
|
|
18
17
|
|
|
19
18
|
## Installation
|
|
@@ -54,12 +53,10 @@ src/
|
|
|
54
53
|
http-provider.ts # HTTP prompt source
|
|
55
54
|
sub-agent-prompt.ts # AgentProfile type + subagent prompt assembly + profile resolution
|
|
56
55
|
tool-gated-sections.ts # requires-capability marker gating
|
|
57
|
-
environment-context.ts # environment context collection and formatting
|
|
58
56
|
lesson-injection.ts # learned-experience formatting and injection
|
|
59
|
-
skill-loop-guidance.ts # skill-loop guidance (RFC-318)
|
|
60
57
|
index.ts # barrel export
|
|
61
|
-
prompts/ # built-in templates: lead-guidance.md, lesson/runtime-lessons.md
|
|
62
|
-
tests/ #
|
|
58
|
+
prompts/ # built-in templates: lead-guidance.md, skill-loop-guidance.md, lesson/runtime-lessons.md
|
|
59
|
+
tests/ # 4 test files
|
|
63
60
|
```
|
|
64
61
|
|
|
65
62
|
## Development Commands
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AgentProfile, AgentProfile as AgentProfile$1 } from "@x-otto/agent";
|
|
2
|
+
|
|
1
3
|
//#region src/types.d.ts
|
|
2
4
|
interface PromptEntry {
|
|
3
5
|
key: string;
|
|
@@ -69,23 +71,6 @@ declare function createPromptProvider(options: PromptProviderOptions): PromptPro
|
|
|
69
71
|
//#endregion
|
|
70
72
|
//#region src/sub-agent-prompt.d.ts
|
|
71
73
|
declare const SUBAGENT_GUARDRAILS: string;
|
|
72
|
-
interface AgentProfile {
|
|
73
|
-
name: string;
|
|
74
|
-
description: string;
|
|
75
|
-
taskTypes: string[];
|
|
76
|
-
capabilities: string[];
|
|
77
|
-
systemPromptTemplate: string;
|
|
78
|
-
/** 提示词注入模式。'append'(缺省)= 角色块注入首条 user message;'replace' = 整体替换 system prompt */
|
|
79
|
-
promptMode?: 'append' | 'replace';
|
|
80
|
-
defaultTools?: string[];
|
|
81
|
-
/** 黑名单工具——后置过滤,对所有来源(白名单/explicitTools)生效 */
|
|
82
|
-
disallowedTools?: string[];
|
|
83
|
-
preferredModelTier?: string;
|
|
84
|
-
defaultMaxToolTurns?: number;
|
|
85
|
-
defaultMaxToolTurnExtensions?: number;
|
|
86
|
-
/** 是否允许子代理再委托(缺省 false) */
|
|
87
|
-
allowSubagents?: boolean;
|
|
88
|
-
}
|
|
89
74
|
interface SubAgentPromptContext {
|
|
90
75
|
taskTitle?: string;
|
|
91
76
|
taskDescription?: string;
|
|
@@ -94,14 +79,19 @@ interface SubAgentPromptContext {
|
|
|
94
79
|
/**
|
|
95
80
|
* Assemble the system prompt for a sub-agent based on agent profile and task context.
|
|
96
81
|
* Replaces `{task_*}` placeholders.
|
|
82
|
+
*
|
|
83
|
+
* 未知占位符 fail-loud(不静默):替换完成后若模板仍有 `{word}` 残留(用户自定义 profile
|
|
84
|
+
* 写了 `{task_*}` 之外的占位符),console.warn 提示——防模型看到未替换的占位符原文。
|
|
85
|
+
* 只 warn 不抛错:内置三占位符有 'not provided' 缺省(buildContextReplacements),
|
|
86
|
+
* 残留只可能来自自定义模板的笔误/扩展占位符,warn 足够暴露问题。
|
|
97
87
|
*/
|
|
98
|
-
declare function assembleSubAgentPrompt(profile: AgentProfile, context?: SubAgentPromptContext): string;
|
|
88
|
+
declare function assembleSubAgentPrompt(profile: AgentProfile$1, context?: SubAgentPromptContext): string;
|
|
99
89
|
declare function assembleGenericSubAgentPrompt(agentName: string, context?: SubAgentPromptContext): string;
|
|
100
90
|
/**
|
|
101
91
|
* Find the matching profile from agent-profiles data.
|
|
102
92
|
* Prefers exact match by name, then fuzzy match by capabilities.
|
|
103
93
|
*/
|
|
104
|
-
declare function resolveAgentProfile(profiles: AgentProfile[], agentName: string): AgentProfile | undefined;
|
|
94
|
+
declare function resolveAgentProfile(profiles: AgentProfile$1[], agentName: string): AgentProfile$1 | undefined;
|
|
105
95
|
//#endregion
|
|
106
96
|
//#region src/prompt-manager.d.ts
|
|
107
97
|
type PromptPreset = 'main' | 'subagent';
|
|
@@ -125,6 +115,21 @@ interface PromptManagerOptions {
|
|
|
125
115
|
*/
|
|
126
116
|
knownCapabilities?: ReadonlySet<string>;
|
|
127
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* 结构化 prompt 组装产物。将 stable 前缀(进 prompt cache)与 volatile 尾段(cache 断点之后)
|
|
120
|
+
* 分离,让下游不再字符串拼接而是按 lane 取用。
|
|
121
|
+
*
|
|
122
|
+
* - `stable`:会话内稳定的 system 前缀(lead-guidance.md 经能力门控后的内容)。
|
|
123
|
+
* - `volatile`:逐轮可变的尾段。PromptManager 当前不产生 volatile 内容(动态段由
|
|
124
|
+
* @x-otto/runtime 的 hook 体系注入),此字段为结构预留——未来 PromptManager 可注册
|
|
125
|
+
* section 时自然填充。
|
|
126
|
+
* - `render()`:拼接 stable + volatile 的便捷方法(向后兼容 assemblePreset 的 string 返回)。
|
|
127
|
+
*/
|
|
128
|
+
interface PromptAssembly {
|
|
129
|
+
readonly stable: string;
|
|
130
|
+
readonly volatile: string;
|
|
131
|
+
render(): string;
|
|
132
|
+
}
|
|
128
133
|
declare class PromptManager {
|
|
129
134
|
private readonly provider;
|
|
130
135
|
private readonly knownCapabilities?;
|
|
@@ -132,13 +137,15 @@ declare class PromptManager {
|
|
|
132
137
|
constructor(options: PromptManagerOptions);
|
|
133
138
|
load(key: string): Promise<string | null>;
|
|
134
139
|
/**
|
|
140
|
+
* 组装 lead-guidance.md 为结构化 PromptAssembly。
|
|
141
|
+
*
|
|
135
142
|
* `enabledCapabilities`:本会话已解析的能力键集合(RFC-101,见 `tool-gated-sections.ts`)。传入时
|
|
136
143
|
* 对 `lead-guidance.md` 中 `<!-- requires-capability: X -->` 标记的段落做门控——`X` 不在集合中则
|
|
137
144
|
* 剔除该段落,消除悬空引用。缺省(`undefined`)保持向后兼容:不剔除任何段落内容,仅清理标记语法本身。
|
|
138
145
|
* 能力键本身不是工具名(RFC-057 D9/M94-01:能力层不得硬编码宿主工具名)——具体映射由宿主层
|
|
139
146
|
* (`@x-otto/coding`)的 `CAPABILITY_TOOL_MAP` 负责,本层只消费已转换好的能力键集合。
|
|
140
147
|
*/
|
|
141
|
-
assemble(enabledCapabilities?: ReadonlySet<string>): Promise<
|
|
148
|
+
assemble(enabledCapabilities?: ReadonlySet<string>): Promise<PromptAssembly>;
|
|
142
149
|
assemblePreset(options?: PromptPresetOptions, enabledCapabilities?: ReadonlySet<string>): Promise<string>;
|
|
143
150
|
/**
|
|
144
151
|
* append 模式子代理的**角色块** —— 渲染后的 profile 模板,由宿主注入为
|
|
@@ -156,37 +163,6 @@ declare const BUILTIN_PROMPTS_DIR: string;
|
|
|
156
163
|
//#region src/lesson-injection.d.ts
|
|
157
164
|
declare function buildLessonInjection(lessons: Lesson[], template?: string): string;
|
|
158
165
|
//#endregion
|
|
159
|
-
//#region src/skill-loop-guidance.d.ts
|
|
160
|
-
/**
|
|
161
|
-
* skill-loop-guidance.ts —— RFC-318 D7:三分流判别段。
|
|
162
|
-
*
|
|
163
|
-
* 解决的问题:模型遇到"这事我做起来很别扭"时,没有规范告诉它该走哪条路——结果要么从不
|
|
164
|
-
* 触发自迭代(回路空转),要么逢事就提议造插件(骚扰)。本段给出分流判据。
|
|
165
|
-
*
|
|
166
|
-
* **R8 单源纪律(硬约束)**:本段只写**判据**(什么情况归哪条路),不写各条路的执行细节。
|
|
167
|
-
* - "缺工具之后具体怎么做"在 `capability_gap` 工具自己的 guidance 里(tool-nodes.ts);
|
|
168
|
-
* - "技能回路怎么观测、怎么提案"在 RFC-318 与提案简报里。
|
|
169
|
-
* 三处各说各的一部分。任何在此处复述另外两处内容的改动都违反 R8——那会制造分裂真源,
|
|
170
|
-
* 且平白消耗每轮的 prompt 预算。
|
|
171
|
-
*
|
|
172
|
-
* 措辞要点:
|
|
173
|
-
* - 第二条明确**不需要模型做任何事**(otto 在后台观测),避免模型自作主张去"记录"什么;
|
|
174
|
-
* - 末条 "Do not announce either of the above" 是防噪声——没有这句,模型会在每个普通任务后
|
|
175
|
-
* 附一段"这不属于能力缺口"的废话。
|
|
176
|
-
*/
|
|
177
|
-
declare const SKILL_LOOP_GUIDANCE = "Capability triage \u2014 when a request feels hard to fulfill, classify it first:\n\n- You lack a TOOL or integration that would be needed \u2192 use capability_gap.\n- You have everything needed, but you notice you've repeated the same multi-step routine\n many times in this project \u2192 nothing to do; otto observes repeated routines in the\n background and will offer to save one as a reusable skill.\n- Anything else \u2192 just do the task.\n\nDo not announce this triage or narrate which branch applied.";
|
|
178
|
-
//#endregion
|
|
179
|
-
//#region src/environment-context.d.ts
|
|
180
|
-
interface Environment {
|
|
181
|
-
workspaceDir: string;
|
|
182
|
-
date: string;
|
|
183
|
-
platform: string;
|
|
184
|
-
gitBranch?: string;
|
|
185
|
-
gitStatus?: string;
|
|
186
|
-
}
|
|
187
|
-
declare function collectEnvironment(workspaceDir: string, exec?: (cmd: string, cwd: string) => Promise<string>): Promise<Environment>;
|
|
188
|
-
declare function formatEnvironmentBlock(env: Environment): string;
|
|
189
|
-
//#endregion
|
|
190
166
|
//#region src/tool-gated-sections.d.ts
|
|
191
167
|
/**
|
|
192
168
|
* tool-gated-sections.ts
|
|
@@ -217,5 +193,5 @@ declare function gateByToolAvailability(content: string, enabledCapabilities: Re
|
|
|
217
193
|
onUnclosedTag?: (capability: string) => void;
|
|
218
194
|
}): string;
|
|
219
195
|
//#endregion
|
|
220
|
-
export { type AgentProfile, BUILTIN_PROMPTS_DIR,
|
|
196
|
+
export { type AgentProfile, BUILTIN_PROMPTS_DIR, HttpPromptProvider, type Lesson, LocalPromptProvider, type LocalProviderOptions, type PromptAssembly, type PromptEntry, PromptManager, type PromptManagerOptions, type PromptPreset, type PromptPresetOptions, type PromptProvider, type PromptProviderOptions, type RemoteProviderOptions, SUBAGENT_GUARDRAILS, type SubAgentPromptContext, assembleGenericSubAgentPrompt, assembleSubAgentPrompt, buildLessonInjection, createPromptProvider, gateByToolAvailability, resolveAgentProfile };
|
|
221
197
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/local-provider.ts","../src/http-provider.ts","../src/prompt-factory.ts","../src/sub-agent-prompt.ts","../src/prompt-manager.ts","../src/constants.ts","../src/lesson-injection.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/local-provider.ts","../src/http-provider.ts","../src/prompt-factory.ts","../src/sub-agent-prompt.ts","../src/prompt-manager.ts","../src/constants.ts","../src/lesson-injection.ts","../src/tool-gated-sections.ts"],"mappings":";;;UAAiB,WAAA;EACf,GAAA;EACA,OAAA;AAAA;AAAA,UAGe,cAAA;EACf,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,WAAA;EAC3B,IAAA,IAAQ,OAAA;AAAA;AAAA,UAGO,oBAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA;AAAA;AAAA,UAGe,qBAAA;EACf,IAAA;EACA,OAAA;EACA,OAAA,SAAgB,OAAA;IAAU,KAAA;EAAA;EAC1B,UAAA,SAAmB,OAAA,CAAQ,MAAA;EAC3B,KAAA,UAAe,UAAA,CAAW,KAAA;EAC1B,SAAA;AAAA;AAAA,KAGU,qBAAA,GAAwB,oBAAA,GAAuB,qBAAA;AAAA,UAE1C,MAAA;EACf,IAAA;EACA,OAAA;EACA,OAAA;AAAA;;;;;AA9BF;;;;;AAKA;cCOa,mBAAA,YAA+B,cAAA;EAAA,iBACzB,OAAA;EAAA,iBACA,SAAA;cAEL,OAAA,EAAS,IAAA,CAAK,oBAAA;EAKpB,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,WAAA;EAa3B,IAAA,CAAA,GAAQ,OAAA;EAAA,QAIA,IAAA;AAAA;;;cCpCH,kBAAA,YAA8B,cAAA;EAAA,iBACxB,OAAA;EAAA,iBACA,OAAA;EAAA,iBACA,UAAA;EAAA,iBACA,KAAA;EAAA,iBACA,SAAA;cAEL,OAAA,EAAS,IAAA,CAAK,qBAAA;EAQpB,IAAA,CAAK,GAAA,WAAc,OAAA,CAAQ,WAAA;EAe3B,IAAA,CAAA,GAAQ,OAAA;EAAA,QAQA,OAAA;AAAA;;;;;AFxChB;;iBGQgB,oBAAA,CAAqB,OAAA,EAAS,qBAAA,GAAwB,cAAA;;;cCFzD,mBAAA;AAAA,UA2BI,qBAAA;EACf,SAAA;EACA,eAAA;EACA,SAAA;AAAA;AJ/BF;;;;;;;;;AAAA,iBImDgB,sBAAA,CACd,OAAA,EAAS,cAAA,EACT,OAAA,GAAS,qBAAA;AAAA,iBA6BK,6BAAA,CACd,SAAA,UACA,OAAA,GAAS,qBAAA;;;;;iBAkBK,mBAAA,CACd,QAAA,EAAU,cAAA,IACV,SAAA,WACC,cAAA;;;KCrGS,YAAA;AAAA,KAEA,mBAAA;EAEN,MAAA;AAAA;EAGA,MAAA;EACA,SAAA;EACA,OAAA,GAAU,YAAA;EACV,OAAA,GAAU,qBAAA;EACV,UAAA;AAAA;AAAA,UAKW,oBAAA;EACf,QAAA,EAAU,cAAA;ELnBK;;;;;;;EK2Bf,iBAAA,GAAoB,WAAA;AAAA;;ALxBtB;;;;;;;;;UKqCiB,cAAA;EAAA,SACN,MAAA;EAAA,SACA,QAAA;EACT,MAAA;AAAA;AAAA,cAGW,aAAA;EAAA,iBACM,QAAA;EAAA,iBACA,iBAAA;EAAA,QACT,YAAA;cAEI,OAAA,EAAS,oBAAA;EAKf,IAAA,CAAK,GAAA,WAAc,OAAA;EL5CzB;;;;;;;;;EK0DM,QAAA,CAAS,mBAAA,GAAsB,WAAA,WAAsB,OAAA,CAAQ,cAAA;EAqB7D,cAAA,CACJ,OAAA,GAAS,mBAAA,EACT,mBAAA,GAAsB,WAAA,WACrB,OAAA;EL/EM;AAGX;;;;;EKgGE,oBAAA,CAAqB,OAAA,EAAS,mBAAA;EAUxB,0BAAA,CAAA,GAA8B,OAAA;AAAA;;;cC9HzB,mBAAA;;;iBCHG,oBAAA,CAAqB,OAAA,EAAS,MAAA,IAAU,QAAA;;;;;;APFxD;;;;;AAKA;;;;;;;;;;;;;;;iBQ4BgB,sBAAA,CACd,OAAA,UACA,mBAAA,EAAqB,WAAA,sBACrB,OAAA;ER1Be,+CQ4Bb,iBAAA,GAAoB,WAAA;EACpB,mBAAA,IAAuB,UAAA;EACvB,aAAA,IAAiB,UAAA;AAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
1
|
import{readFile as e,readdir as t}from"node:fs/promises";import{dirname as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import{fileURLToPath as s}from"node:url";var c=class{baseDir;extension;constructor(e){this.baseDir=e.baseDir,this.extension=e.extension??`.md`}async load(t){let n=r(this.baseDir,`${t}${this.extension}`);try{return{key:t,content:(await e(n,`utf-8`)).trim()}}catch{return null}}async list(){return this.walk(this.baseDir)}async walk(e){let n=[];try{let a=await t(e,{withFileTypes:!0});for(let t of a){let a=r(e,t.name);if(t.isDirectory()){let e=await this.walk(a);n.push(...e)}else if(t.name.endsWith(this.extension)){let e=i(this.baseDir,a).slice(0,-this.extension.length).split(o).join(`/`);n.push(e)}}}catch{}return n}},l=class{baseUrl;getAuth;getHeaders;fetch;timeoutMs;constructor(e){this.baseUrl=e.baseUrl.replace(/\/+$/,``),this.getAuth=e.getAuth,this.getHeaders=e.getHeaders,this.fetch=e.fetch??globalThis.fetch,this.timeoutMs=e.timeoutMs??1e4}async load(e){try{let t=await this.request(`/prompts/${encodeURIComponent(e)}`);return t.ok?await t.json():null}catch{return null}}async list(){let e=await this.request(`/prompts`);if(!e.ok)throw Error(`Remote prompt list failed: ${e.status}`);return await e.json()}async request(e,t){let n={"Content-Type":`application/json`};if(this.getAuth){let{token:e}=await this.getAuth();n.Authorization=`Bearer ${e}`}this.getHeaders&&Object.assign(n,await this.getHeaders());let r=new AbortController,i=setTimeout(()=>r.abort(),this.timeoutMs);try{return await this.fetch(`${this.baseUrl}${e}`,{...t,headers:{...n,...t?.headers},signal:r.signal})}finally{clearTimeout(i)}}};function u(e){switch(e.type){case`local`:return new c({baseDir:e.baseDir,extension:e.extension});case`remote`:return new l({baseUrl:e.baseUrl,getAuth:e.getAuth,getHeaders:e.getHeaders,fetch:e.fetch,timeoutMs:e.timeoutMs});default:throw Error(`Unknown prompt provider type: ${e.type}`)}}const d=[`You are a sub-agent: do not interact with the user directly; do not expand the task scope; on completion, report your conclusion and any remaining risks.`].join(`
|
|
2
|
-
`)
|
|
3
|
-
`)}function h(e,t){let n=e.find(e=>e.name===t);if(n)return n;let r=t.toLowerCase();return e.find(e=>r.endsWith(e.name.toLowerCase())||e.capabilities.some(e=>r.includes(e.toLowerCase()))||e.taskTypes.some(e=>r.includes(e.toLowerCase())))}const
|
|
2
|
+
`),f=[`You are a sub-agent "{agent_name}" in the Otto system.`,`You are responsible for executing one clearly scoped sub-task. Do not interact with users directly, handle overall planning, or further delegate to other sub-agents.`,``,`## Current Task`,`- Title: {task_title}`,`- Description: {task_description}`,`- File scope: {task_files}`,``,`## Execution Requirements`,`- Stay focused on the current task; do not expand scope.`,`- Read relevant code before making changes.`,`- After completion, provide a concise conclusion and note any remaining risks or blockers.`].join(`
|
|
3
|
+
`);function p(e){return{"{task_title}":e.taskTitle??`not provided`,"{task_description}":e.taskDescription??`not provided`,"{task_files}":e.taskFiles?.join(`, `)??`not provided`}}function m(e,t={}){let n=e.systemPromptTemplate,r=p(t);for(let[e,t]of Object.entries(r))n=n.replaceAll(e,t);return h(n,e.name),n}function h(e,t){let n=new Set;for(let t of e.matchAll(/\{([a-z][a-z0-9_]*)\}/gi))n.add(t[0]);n.size>0&&console.warn(`[prompt] sub-agent profile "${t}" template has unresolved placeholder(s): ${[...n].join(`, `)} — expected {task_title}/{task_description}/{task_files}. The literal placeholder will reach the model; fix the template or extend the replacement map.`)}function g(e,t={}){let n={...p(t),"{agent_name}":e},r=f;for(let[e,t]of Object.entries(n))r=r.replaceAll(e,t);return r}function _(e,t){let n=e.find(e=>e.name===t);if(n)return n;let r=t.toLowerCase();return e.find(e=>r.endsWith(e.name.toLowerCase())||e.capabilities.some(e=>r.includes(e.toLowerCase()))||e.taskTypes.some(e=>r.includes(e.toLowerCase())))}const v=`[a-zA-Z0-9][a-zA-Z0-9-]*`,y=RegExp(`<!--\\s*requires-capability:\\s*(${v})\\s*-->([\\s\\S]*?)<!--\\s*/requires-capability\\s*-->`,`g`),b=RegExp(`<!--\\s*requires-capability:\\s*(${v})\\s*-->`,`g`);function x(e,t,n){let r=e.replace(y,(e,r,i)=>(n?.knownCapabilities&&!n.knownCapabilities.has(r)&&n.onUnknownCapability?.(r),t?t.has(r)?i:``:i));if(n?.onUnclosedTag)for(let e of r.matchAll(b))n.onUnclosedTag(e[1]??``);return r}var S=class{provider;knownCapabilities;leadGuidance=null;constructor(e){this.provider=e.provider,this.knownCapabilities=e.knownCapabilities}async load(e){return(await this.provider.load(e))?.content??null}async assemble(e){this.leadGuidance===null&&(this.leadGuidance=await this.load(`lead-guidance`)??``);let t=x(this.leadGuidance,e,{knownCapabilities:this.knownCapabilities,onUnknownCapability:e=>console.warn(`[prompt] lead-guidance.md references unknown capability "${e}" (typo or renamed key in CAPABILITY_TOOL_MAP?) — the gated section is silently dropped`),onUnclosedTag:e=>console.warn(`[prompt] lead-guidance.md has an unclosed requires-capability tag: "${e}"`)});return{stable:t,volatile:``,render:()=>t}}async assemblePreset(e={preset:`main`},t){return e.preset===`subagent`?e.profile?e.profile.promptMode===`append`?(await this.assemble(t)).render():m(e.profile,e.context):g(e.agentName,e.context):(await this.assemble(t)).render()}assembleSubAgentTail(e){if(e.preset===`subagent`&&e.profile&&e.profile.promptMode===`append`)return m(e.profile,e.context)}async loadRuntimeLessonsTemplate(){return this.load(`lesson/runtime-lessons`)}};const C=a(n(s(import.meta.url)),`..`,`prompts`);function w(e,t){if(e.length===0)return``;let n=e.map((e,t)=>`${t+1}. [${e.tags.join(`, `)}] ${e.trigger} → ${e.insight}`);return(t??`### Runtime Lessons
|
|
4
4
|
The following lessons were learned from previous sessions:
|
|
5
5
|
{lessons}`).replace(`{lessons}`,n.join(`
|
|
6
|
-
`))}
|
|
7
|
-
|
|
8
|
-
- You lack a TOOL or integration that would be needed → use capability_gap.
|
|
9
|
-
- You have everything needed, but you notice you've repeated the same multi-step routine
|
|
10
|
-
many times in this project → nothing to do; otto observes repeated routines in the
|
|
11
|
-
background and will offer to save one as a reusable skill.
|
|
12
|
-
- Anything else → just do the task.
|
|
13
|
-
|
|
14
|
-
Do not announce this triage or narrate which branch applied.`;async function w(e,t){let n={workspaceDir:e,date:new Date().toISOString().split(`T`)[0]??new Date().toLocaleDateString(),platform:`${process.platform}/${process.arch}`};if(!t)return n;try{let r=(await t(`git rev-parse --abbrev-ref HEAD`,e)).trim();r&&(n.gitBranch=r)}catch{}try{let r=(await t(`git status --porcelain --short`,e)).trim();if(r){let e=r.split(`
|
|
15
|
-
`);n.gitStatus=e.length>10?`${e.slice(0,10).join(`
|
|
16
|
-
`)}\n... and ${e.length-10} more files`:r}}catch{}return n}function T(e){let t=[`### Runtime Environment`,`- Working directory: ${e.workspaceDir}`,`- Date: ${e.date}`,`- Platform: ${e.platform}`];return e.gitBranch&&t.push(`- Git branch: ${e.gitBranch}`),e.gitStatus&&t.push(`- Git status:\n\`\`\`\n${e.gitStatus}\n\`\`\``),t.join(`
|
|
17
|
-
`)}export{x as BUILTIN_PROMPTS_DIR,l as HttpPromptProvider,c as LocalPromptProvider,b as PromptManager,C as SKILL_LOOP_GUIDANCE,d as SUBAGENT_GUARDRAILS,m as assembleGenericSubAgentPrompt,p as assembleSubAgentPrompt,S as buildLessonInjection,w as collectEnvironment,u as createPromptProvider,T as formatEnvironmentBlock,y as gateByToolAvailability,h as resolveAgentProfile};
|
|
6
|
+
`))}export{C as BUILTIN_PROMPTS_DIR,l as HttpPromptProvider,c as LocalPromptProvider,S as PromptManager,d as SUBAGENT_GUARDRAILS,g as assembleGenericSubAgentPrompt,m as assembleSubAgentPrompt,w as buildLessonInjection,u as createPromptProvider,x as gateByToolAvailability,_ as resolveAgentProfile};
|
|
18
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/local-provider.ts","../src/http-provider.ts","../src/prompt-factory.ts","../src/sub-agent-prompt.ts","../src/tool-gated-sections.ts","../src/prompt-manager.ts","../src/constants.ts","../src/lesson-injection.ts","../src/skill-loop-guidance.ts","../src/environment-context.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises'\nimport { join, relative, sep } from 'node:path'\nimport type { LocalProviderOptions, PromptEntry, PromptProvider } from './types'\n\n/**\n * Local file system prompt provider\n * Reads markdown files from the specified directory as prompt content\n *\n * Directory structure maps to key:\n * baseDir/lead-guidance.md → \"lead-guidance\"\n * baseDir/lesson/runtime-lessons.md → \"lesson/runtime-lessons\"\n */\nexport class LocalPromptProvider implements PromptProvider {\n private readonly baseDir: string\n private readonly extension: string\n\n constructor(options: Omit<LocalProviderOptions, 'type'>) {\n this.baseDir = options.baseDir\n this.extension = options.extension ?? '.md'\n }\n\n async load(key: string): Promise<PromptEntry | null> {\n const filePath = join(this.baseDir, `${key}${this.extension}`)\n try {\n const content = await readFile(filePath, 'utf-8')\n return {\n key,\n content: content.trim(),\n }\n } catch {\n return null\n }\n }\n\n async list(): Promise<string[]> {\n return this.walk(this.baseDir)\n }\n\n private async walk(dir: string): Promise<string[]> {\n const keys: string[] = []\n try {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory()) {\n const subKeys = await this.walk(fullPath)\n keys.push(...subKeys)\n } else if (entry.name.endsWith(this.extension)) {\n const rel = relative(this.baseDir, fullPath)\n const key = rel.slice(0, -this.extension.length).split(sep).join('/')\n keys.push(key)\n }\n }\n } catch {}\n return keys\n }\n}\n","import type { RemoteProviderOptions, PromptEntry, PromptProvider } from './types'\n\nexport class HttpPromptProvider implements PromptProvider {\n private readonly baseUrl: string\n private readonly getAuth?: () => Promise<{ token: string }>\n private readonly getHeaders?: () => Promise<Record<string, string>>\n private readonly fetch: typeof globalThis.fetch\n private readonly timeoutMs: number\n\n constructor(options: Omit<RemoteProviderOptions, 'type'>) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.getAuth = options.getAuth\n this.getHeaders = options.getHeaders\n this.fetch = options.fetch ?? globalThis.fetch\n this.timeoutMs = options.timeoutMs ?? 10_000\n }\n\n async load(key: string): Promise<PromptEntry | null> {\n try {\n const response = await this.request(`/prompts/${encodeURIComponent(key)}`)\n // Fail-soft: a single prompt load is best-effort. Any non-ok status\n // (404 or server error) and any network/parse failure resolves to null\n // (treated as not-found). list() deliberately differs and throws.\n if (!response.ok) {\n return null\n }\n return (await response.json()) as PromptEntry\n } catch {\n return null\n }\n }\n\n async list(): Promise<string[]> {\n const response = await this.request('/prompts')\n if (!response.ok) {\n throw new Error(`Remote prompt list failed: ${response.status}`)\n }\n return (await response.json()) as string[]\n }\n\n private async request(path: string, init?: RequestInit): Promise<Response> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.getAuth) {\n const { token } = await this.getAuth()\n headers['Authorization'] = `Bearer ${token}`\n }\n if (this.getHeaders) {\n Object.assign(headers, await this.getHeaders())\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n\n try {\n return await this.fetch(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...headers, ...(init?.headers as Record<string, string>) },\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n}\n","import { LocalPromptProvider } from './local-provider'\nimport { HttpPromptProvider } from './http-provider'\nimport type { PromptProvider, PromptProviderOptions } from './types'\n\n/**\n * Create the corresponding PromptProvider instance based on options.\n * Pattern is consistent with createPersistence in the persistence package.\n */\nexport function createPromptProvider(options: PromptProviderOptions): PromptProvider {\n switch (options.type) {\n case 'local':\n return new LocalPromptProvider({\n baseDir: options.baseDir,\n extension: options.extension,\n })\n case 'remote':\n return new HttpPromptProvider({\n baseUrl: options.baseUrl,\n getAuth: options.getAuth,\n getHeaders: options.getHeaders,\n fetch: options.fetch,\n timeoutMs: options.timeoutMs,\n })\n default:\n throw new Error(`Unknown prompt provider type: ${(options as { type: string }).type}`)\n }\n}\n","export const SUBAGENT_GUARDRAILS = [\n 'You are a sub-agent: do not interact with the user directly; do not expand the task scope; on completion, report your conclusion and any remaining risks.',\n].join('\\n')\n\nexport interface AgentProfile {\n name: string\n description: string\n taskTypes: string[]\n capabilities: string[]\n systemPromptTemplate: string\n /** 提示词注入模式。'append'(缺省)= 角色块注入首条 user message;'replace' = 整体替换 system prompt */\n promptMode?: 'append' | 'replace'\n defaultTools?: string[]\n /** 黑名单工具——后置过滤,对所有来源(白名单/explicitTools)生效 */\n disallowedTools?: string[]\n preferredModelTier?: string\n defaultMaxToolTurns?: number\n defaultMaxToolTurnExtensions?: number\n /** 是否允许子代理再委托(缺省 false) */\n allowSubagents?: boolean\n}\n\nexport interface SubAgentPromptContext {\n taskTitle?: string\n taskDescription?: string\n taskFiles?: string[]\n}\n\nfunction buildContextReplacements(context: SubAgentPromptContext): Record<string, string> {\n return {\n '{task_title}': context.taskTitle ?? 'not provided',\n '{task_description}': context.taskDescription ?? 'not provided',\n '{task_files}': context.taskFiles?.join(', ') ?? 'not provided',\n }\n}\n\n/**\n * Assemble the system prompt for a sub-agent based on agent profile and task context.\n * Replaces `{task_*}` placeholders.\n */\nexport function assembleSubAgentPrompt(\n profile: AgentProfile,\n context: SubAgentPromptContext = {},\n): string {\n let prompt = profile.systemPromptTemplate\n\n const replacements = buildContextReplacements(context)\n\n for (const [placeholder, value] of Object.entries(replacements)) {\n prompt = prompt.replaceAll(placeholder, value)\n }\n\n return prompt\n}\n\nexport function assembleGenericSubAgentPrompt(\n agentName: string,\n context: SubAgentPromptContext = {},\n): string {\n const replacements = buildContextReplacements(context)\n\n return [\n `You are a sub-agent \"${agentName}\" in the Otto system.`,\n 'You are responsible for executing one clearly scoped sub-task. Do not interact with users directly, handle overall planning, or further delegate to other sub-agents.',\n '',\n\n '## Current Task',\n `- Title: ${replacements['{task_title}']}`,\n `- Description: ${replacements['{task_description}']}`,\n `- File scope: ${replacements['{task_files}']}`,\n '',\n '## Execution Requirements',\n '- Stay focused on the current task; do not expand scope.',\n '- Read relevant code before making changes.',\n '- After completion, provide a concise conclusion and note any remaining risks or blockers.',\n ].join('\\n')\n}\n\n/**\n * Find the matching profile from agent-profiles data.\n * Prefers exact match by name, then fuzzy match by capabilities.\n */\nexport function resolveAgentProfile(\n profiles: AgentProfile[],\n agentName: string,\n): AgentProfile | undefined {\n const exact = profiles.find((p) => p.name === agentName)\n if (exact) {\n return exact\n }\n\n const lower = agentName.toLowerCase()\n return profiles.find(\n (p) =>\n lower.endsWith(p.name.toLowerCase()) ||\n p.capabilities.some((c) => lower.includes(c.toLowerCase())) ||\n p.taskTypes.some((t) => lower.includes(t.toLowerCase())),\n )\n}\n","/**\n * tool-gated-sections.ts\n *\n * RFC-101:lead-guidance.md 按已解析能力键集合分段门控。\n *\n * markdown 内联标记 `<!-- requires-capability: X -->...<!-- /requires-capability -->` 标注段落对\n * 特定**能力**(非具体工具名)的依赖。`gateByToolAvailability` 在组装期扫描并剔除能力不可用的\n * 段落(含标记本身),消除悬空引用(如 minimal/standard 工具集会话看到引用了不存在能力的操作指引)。\n *\n * RFC-057 §3 D9 / M94-01:`packages/prompt` 是能力层,不得硬编码宿主工具名字面量——本模块与\n * `lead-guidance.md` 只认识语义能力键(如 `delegation`/`task-observability`),能力键到具体工具名\n * 的映射表下沉到宿主层(`@x-otto/coding`)。\n *\n * 纯函数,不访问全局状态、不做 I/O(RFC-101 重要事项规则 1)。\n */\n\n// 能力键字符集:字母/数字/连字符(如 `task-observability`),首字符不能是连字符,\n// 避免与标记结尾的 ` -->` 产生贪婪匹配歧义。\nconst CAPABILITY_NAME = '[a-zA-Z0-9][a-zA-Z0-9-]*'\nconst REQUIRES_CAPABILITY_PATTERN = new RegExp(\n `<!--\\\\s*requires-capability:\\\\s*(${CAPABILITY_NAME})\\\\s*-->([\\\\s\\\\S]*?)<!--\\\\s*/requires-capability\\\\s*-->`,\n 'g',\n)\nconst OPEN_TAG_PATTERN = new RegExp(`<!--\\\\s*requires-capability:\\\\s*(${CAPABILITY_NAME})\\\\s*-->`, 'g')\n\n/**\n * 扫描 `content` 中的 `requires-capability` 标记区块:\n * - `enabledCapabilities` 为 `undefined` → 不做任何剔除,但仍清理标记语法(标记本身不应泄漏到最终 prompt)。\n * - 标记的能力键在 `enabledCapabilities` 中 → 保留区块内容,剔除标记。\n * - 标记的能力键不在 `enabledCapabilities` 中 → 整段剔除(含内容与标记)。\n * - 未闭合标记(无匹配 `/requires-capability`)→ 保守处理:不剔除任何内容,原样保留(含标记本身),\n * 并触发 `onUnclosedTag`(RFC-101 重要事项规则 6:system prompt 组装失败是致命故障,必须优雅降级)。\n */\nexport function gateByToolAvailability(\n content: string,\n enabledCapabilities: ReadonlySet<string> | undefined,\n options?: {\n /** 已知能力键全集(用于检测标记拼写错误/能力已重命名)。缺省时不做未知能力键检测。 */\n knownCapabilities?: ReadonlySet<string>\n onUnknownCapability?: (capability: string) => void\n onUnclosedTag?: (capability: string) => void\n },\n): string {\n const gated = content.replace(REQUIRES_CAPABILITY_PATTERN, (_match, capability: string, body: string) => {\n if (options?.knownCapabilities && !options.knownCapabilities.has(capability)) {\n options.onUnknownCapability?.(capability)\n }\n\n if (!enabledCapabilities) {\n return body\n }\n\n return enabledCapabilities.has(capability) ? body : ''\n })\n\n if (options?.onUnclosedTag) {\n // 剩余的开标记(未被上面成对匹配消费掉)即未闭合——原样保留在 gated 中,仅上报观测。\n for (const match of gated.matchAll(OPEN_TAG_PATTERN)) {\n options.onUnclosedTag(match[1] ?? '')\n }\n }\n\n return gated\n}\n","import {\n assembleGenericSubAgentPrompt,\n assembleSubAgentPrompt,\n type AgentProfile,\n type SubAgentPromptContext,\n} from './sub-agent-prompt'\nimport { gateByToolAvailability } from './tool-gated-sections'\nimport type { PromptProvider } from './types'\n\nexport type PromptPreset = 'main' | 'subagent'\n\nexport type PromptPresetOptions =\n | {\n preset?: 'main'\n }\n | {\n preset: 'subagent'\n agentName: string\n profile?: AgentProfile\n context?: SubAgentPromptContext\n promptMode?: 'append' | 'replace'\n }\n\nconst LEAD_GUIDANCE_KEY = 'lead-guidance'\n\nexport interface PromptManagerOptions {\n provider: PromptProvider\n /**\n * 已知能力键全集(终局审查 2026-07-18 S2 接线):传入后 `assemble()` 对 `lead-guidance.md`\n * 中的 `requires-capability` 标记做拼写/漂移检测——标记的能力键不在此集合中时经\n * `onUnknownCapability` 告警(console.warn),防止\"能力键改名/写错 → 段落静默消失\"。\n * 真源是宿主层 `CAPABILITY_TOOL_MAP`(@x-otto/coding capability-tool-map.ts)的键集合,\n * 经组装根注入(本层不依赖 coding,保持 RFC-057 D9 能力层边界)。缺省不检测(向后兼容)。\n */\n knownCapabilities?: ReadonlySet<string>\n}\n\nexport class PromptManager {\n private readonly provider: PromptProvider\n private readonly knownCapabilities?: ReadonlySet<string>\n private leadGuidance: string | null = null\n\n constructor(options: PromptManagerOptions) {\n this.provider = options.provider\n this.knownCapabilities = options.knownCapabilities\n }\n\n async load(key: string): Promise<string | null> {\n const entry = await this.provider.load(key)\n return entry?.content ?? null\n }\n\n /**\n * `enabledCapabilities`:本会话已解析的能力键集合(RFC-101,见 `tool-gated-sections.ts`)。传入时\n * 对 `lead-guidance.md` 中 `<!-- requires-capability: X -->` 标记的段落做门控——`X` 不在集合中则\n * 剔除该段落,消除悬空引用。缺省(`undefined`)保持向后兼容:不剔除任何段落内容,仅清理标记语法本身。\n * 能力键本身不是工具名(RFC-057 D9/M94-01:能力层不得硬编码宿主工具名)——具体映射由宿主层\n * (`@x-otto/coding`)的 `CAPABILITY_TOOL_MAP` 负责,本层只消费已转换好的能力键集合。\n */\n async assemble(enabledCapabilities?: ReadonlySet<string>): Promise<string> {\n if (this.leadGuidance === null) {\n this.leadGuidance = (await this.load(LEAD_GUIDANCE_KEY)) ?? ''\n }\n return gateByToolAvailability(this.leadGuidance, enabledCapabilities, {\n knownCapabilities: this.knownCapabilities,\n onUnknownCapability: (capability) =>\n console.warn(\n `[prompt] lead-guidance.md references unknown capability \"${capability}\" ` +\n '(typo or renamed key in CAPABILITY_TOOL_MAP?) — the gated section is silently dropped',\n ),\n onUnclosedTag: (capability) =>\n console.warn(`[prompt] lead-guidance.md has an unclosed requires-capability tag: \"${capability}\"`),\n })\n }\n\n async assemblePreset(\n options: PromptPresetOptions = { preset: 'main' },\n enabledCapabilities?: ReadonlySet<string>,\n ): Promise<string> {\n if (options.preset === 'subagent') {\n if (!options.profile) {\n return assembleGenericSubAgentPrompt(options.agentName, options.context)\n }\n if (options.profile.promptMode === 'append') {\n return this.assemble(enabledCapabilities)\n }\n return assembleSubAgentPrompt(options.profile, options.context)\n }\n\n return this.assemble(enabledCapabilities)\n }\n\n /**\n * append 模式子代理的**角色块** —— 渲染后的 profile 模板,由宿主注入为\n * **volatile system 尾段**(落在 prompt cache 断点之后)。append 的 system prompt 主体由\n * `assemblePreset` 返回基底(共享、进缓存),角色差异走此尾段——既得专门化又不击穿跨子代理缓存。\n * 仅 append 模式返回值;replace/缺省(模板已是 system prompt 主体)/无 profile 返回 undefined。\n */\n assembleSubAgentTail(options: PromptPresetOptions): string | undefined {\n if (options.preset !== 'subagent') {\n return undefined\n }\n if (options.profile && options.profile.promptMode === 'append') {\n return assembleSubAgentPrompt(options.profile, options.context)\n }\n return undefined\n }\n\n async loadRuntimeLessonsTemplate(): Promise<string | null> {\n return this.load('lesson/runtime-lessons')\n }\n}\n","import { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport const BUILTIN_PROMPTS_DIR = resolve(__dirname, '..', 'prompts')\n","import type { Lesson } from './types'\n\nexport function buildLessonInjection(lessons: Lesson[], template?: string): string {\n if (lessons.length === 0) {\n return ''\n }\n\n const lines = lessons.map(\n (l, i) => `${i + 1}. [${l.tags.join(', ')}] ${l.trigger} → ${l.insight}`,\n )\n\n const t =\n template ??\n `### Runtime Lessons\nThe following lessons were learned from previous sessions:\n{lessons}`\n return t.replace('{lessons}', lines.join('\\n'))\n}\n","/**\n * skill-loop-guidance.ts —— RFC-318 D7:三分流判别段。\n *\n * 解决的问题:模型遇到\"这事我做起来很别扭\"时,没有规范告诉它该走哪条路——结果要么从不\n * 触发自迭代(回路空转),要么逢事就提议造插件(骚扰)。本段给出分流判据。\n *\n * **R8 单源纪律(硬约束)**:本段只写**判据**(什么情况归哪条路),不写各条路的执行细节。\n * - \"缺工具之后具体怎么做\"在 `capability_gap` 工具自己的 guidance 里(tool-nodes.ts);\n * - \"技能回路怎么观测、怎么提案\"在 RFC-318 与提案简报里。\n * 三处各说各的一部分。任何在此处复述另外两处内容的改动都违反 R8——那会制造分裂真源,\n * 且平白消耗每轮的 prompt 预算。\n *\n * 措辞要点:\n * - 第二条明确**不需要模型做任何事**(otto 在后台观测),避免模型自作主张去\"记录\"什么;\n * - 末条 \"Do not announce either of the above\" 是防噪声——没有这句,模型会在每个普通任务后\n * 附一段\"这不属于能力缺口\"的废话。\n */\n\nexport const SKILL_LOOP_GUIDANCE = `Capability triage — when a request feels hard to fulfill, classify it first:\n\n- You lack a TOOL or integration that would be needed → use capability_gap.\n- You have everything needed, but you notice you've repeated the same multi-step routine\n many times in this project → nothing to do; otto observes repeated routines in the\n background and will offer to save one as a reusable skill.\n- Anything else → just do the task.\n\nDo not announce this triage or narrate which branch applied.`\n","export interface Environment {\n workspaceDir: string\n date: string\n platform: string\n gitBranch?: string\n gitStatus?: string\n}\n\nexport async function collectEnvironment(\n workspaceDir: string,\n exec?: (cmd: string, cwd: string) => Promise<string>,\n): Promise<Environment> {\n const snapshot: Environment = {\n workspaceDir,\n date: new Date().toISOString().split('T')[0] ?? new Date().toLocaleDateString(),\n platform: `${process.platform}/${process.arch}`,\n }\n\n if (!exec) {\n return snapshot\n }\n\n try {\n const branch = (await exec('git rev-parse --abbrev-ref HEAD', workspaceDir)).trim()\n if (branch) {\n snapshot.gitBranch = branch\n }\n } catch {}\n\n try {\n const status = (await exec('git status --porcelain --short', workspaceDir)).trim()\n if (status) {\n const lines = status.split('\\n')\n snapshot.gitStatus =\n lines.length > 10\n ? `${lines.slice(0, 10).join('\\n')}\\n... and ${lines.length - 10} more files`\n : status\n }\n } catch {}\n\n return snapshot\n}\n\nexport function formatEnvironmentBlock(env: Environment): string {\n const lines = [\n '### Runtime Environment',\n `- Working directory: ${env.workspaceDir}`,\n `- Date: ${env.date}`,\n `- Platform: ${env.platform}`,\n ]\n\n if (env.gitBranch) {\n lines.push(`- Git branch: ${env.gitBranch}`)\n }\n\n if (env.gitStatus) {\n lines.push(`- Git status:\\n\\`\\`\\`\\n${env.gitStatus}\\n\\`\\`\\``)\n }\n\n return lines.join('\\n')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/local-provider.ts","../src/http-provider.ts","../src/prompt-factory.ts","../src/sub-agent-prompt.ts","../src/tool-gated-sections.ts","../src/prompt-manager.ts","../src/constants.ts","../src/lesson-injection.ts"],"sourcesContent":["import { readFile, readdir } from 'node:fs/promises'\nimport { join, relative, sep } from 'node:path'\nimport type { LocalProviderOptions, PromptEntry, PromptProvider } from './types'\n\n/**\n * Local file system prompt provider\n * Reads markdown files from the specified directory as prompt content\n *\n * Directory structure maps to key:\n * baseDir/lead-guidance.md → \"lead-guidance\"\n * baseDir/lesson/runtime-lessons.md → \"lesson/runtime-lessons\"\n */\nexport class LocalPromptProvider implements PromptProvider {\n private readonly baseDir: string\n private readonly extension: string\n\n constructor(options: Omit<LocalProviderOptions, 'type'>) {\n this.baseDir = options.baseDir\n this.extension = options.extension ?? '.md'\n }\n\n async load(key: string): Promise<PromptEntry | null> {\n const filePath = join(this.baseDir, `${key}${this.extension}`)\n try {\n const content = await readFile(filePath, 'utf-8')\n return {\n key,\n content: content.trim(),\n }\n } catch {\n return null\n }\n }\n\n async list(): Promise<string[]> {\n return this.walk(this.baseDir)\n }\n\n private async walk(dir: string): Promise<string[]> {\n const keys: string[] = []\n try {\n const entries = await readdir(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory()) {\n const subKeys = await this.walk(fullPath)\n keys.push(...subKeys)\n } else if (entry.name.endsWith(this.extension)) {\n const rel = relative(this.baseDir, fullPath)\n const key = rel.slice(0, -this.extension.length).split(sep).join('/')\n keys.push(key)\n }\n }\n } catch {}\n return keys\n }\n}\n","import type { RemoteProviderOptions, PromptEntry, PromptProvider } from './types'\n\nexport class HttpPromptProvider implements PromptProvider {\n private readonly baseUrl: string\n private readonly getAuth?: () => Promise<{ token: string }>\n private readonly getHeaders?: () => Promise<Record<string, string>>\n private readonly fetch: typeof globalThis.fetch\n private readonly timeoutMs: number\n\n constructor(options: Omit<RemoteProviderOptions, 'type'>) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.getAuth = options.getAuth\n this.getHeaders = options.getHeaders\n this.fetch = options.fetch ?? globalThis.fetch\n this.timeoutMs = options.timeoutMs ?? 10_000\n }\n\n async load(key: string): Promise<PromptEntry | null> {\n try {\n const response = await this.request(`/prompts/${encodeURIComponent(key)}`)\n // Fail-soft: a single prompt load is best-effort. Any non-ok status\n // (404 or server error) and any network/parse failure resolves to null\n // (treated as not-found). list() deliberately differs and throws.\n if (!response.ok) {\n return null\n }\n return (await response.json()) as PromptEntry\n } catch {\n return null\n }\n }\n\n async list(): Promise<string[]> {\n const response = await this.request('/prompts')\n if (!response.ok) {\n throw new Error(`Remote prompt list failed: ${response.status}`)\n }\n return (await response.json()) as string[]\n }\n\n private async request(path: string, init?: RequestInit): Promise<Response> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n }\n\n if (this.getAuth) {\n const { token } = await this.getAuth()\n headers['Authorization'] = `Bearer ${token}`\n }\n if (this.getHeaders) {\n Object.assign(headers, await this.getHeaders())\n }\n\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), this.timeoutMs)\n\n try {\n return await this.fetch(`${this.baseUrl}${path}`, {\n ...init,\n headers: { ...headers, ...(init?.headers as Record<string, string>) },\n signal: controller.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n }\n}\n","import { LocalPromptProvider } from './local-provider'\nimport { HttpPromptProvider } from './http-provider'\nimport type { PromptProvider, PromptProviderOptions } from './types'\n\n/**\n * Create the corresponding PromptProvider instance based on options.\n * Pattern is consistent with createPersistence in the persistence package.\n */\nexport function createPromptProvider(options: PromptProviderOptions): PromptProvider {\n switch (options.type) {\n case 'local':\n return new LocalPromptProvider({\n baseDir: options.baseDir,\n extension: options.extension,\n })\n case 'remote':\n return new HttpPromptProvider({\n baseUrl: options.baseUrl,\n getAuth: options.getAuth,\n getHeaders: options.getHeaders,\n fetch: options.fetch,\n timeoutMs: options.timeoutMs,\n })\n default:\n throw new Error(`Unknown prompt provider type: ${(options as { type: string }).type}`)\n }\n}\n","import type { AgentProfile } from '@x-otto/agent'\n\n// 向后兼容 re-export:AgentProfile 已迁移到 @x-otto/agent(agent 领域层),\n// 此 re-export 保持现有消费方 import 路径不变,可逐步迁移到直接从 @x-otto/agent 引用。\nexport type { AgentProfile } from '@x-otto/agent'\n\nexport const SUBAGENT_GUARDRAILS = [\n 'You are a sub-agent: do not interact with the user directly; do not expand the task scope; on completion, report your conclusion and any remaining risks.',\n].join('\\n')\n\n/**\n * 通用子代理 prompt 模板(模块级常量,单源)。\n *\n * 不外部化为 .md:子代理护栏是稳定的**内部行为契约**(几乎不变、非用户可编辑内容),\n * 不满足外部化门槛(对比 lead-guidance 235 行/可远程加载/非开发者可编辑)。外部化会引入\n * .md + .ts fallback 双源(同步调用点 assembleGenericSubAgentPrompt 需降级常量),\n * 为形式牺牲单源,是负优化(2026-08-14 复审回退)。\n */\nconst GENERIC_SUBAGENT_TEMPLATE = [\n 'You are a sub-agent \"{agent_name}\" in the Otto system.',\n 'You are responsible for executing one clearly scoped sub-task. Do not interact with users directly, handle overall planning, or further delegate to other sub-agents.',\n '',\n '## Current Task',\n '- Title: {task_title}',\n '- Description: {task_description}',\n '- File scope: {task_files}',\n '',\n '## Execution Requirements',\n '- Stay focused on the current task; do not expand scope.',\n '- Read relevant code before making changes.',\n '- After completion, provide a concise conclusion and note any remaining risks or blockers.',\n].join('\\n')\n\nexport interface SubAgentPromptContext {\n taskTitle?: string\n taskDescription?: string\n taskFiles?: string[]\n}\n\nfunction buildContextReplacements(context: SubAgentPromptContext): Record<string, string> {\n return {\n '{task_title}': context.taskTitle ?? 'not provided',\n '{task_description}': context.taskDescription ?? 'not provided',\n '{task_files}': context.taskFiles?.join(', ') ?? 'not provided',\n }\n}\n\n/**\n * Assemble the system prompt for a sub-agent based on agent profile and task context.\n * Replaces `{task_*}` placeholders.\n *\n * 未知占位符 fail-loud(不静默):替换完成后若模板仍有 `{word}` 残留(用户自定义 profile\n * 写了 `{task_*}` 之外的占位符),console.warn 提示——防模型看到未替换的占位符原文。\n * 只 warn 不抛错:内置三占位符有 'not provided' 缺省(buildContextReplacements),\n * 残留只可能来自自定义模板的笔误/扩展占位符,warn 足够暴露问题。\n */\nexport function assembleSubAgentPrompt(\n profile: AgentProfile,\n context: SubAgentPromptContext = {},\n): string {\n let prompt = profile.systemPromptTemplate\n\n const replacements = buildContextReplacements(context)\n\n for (const [placeholder, value] of Object.entries(replacements)) {\n prompt = prompt.replaceAll(placeholder, value)\n }\n\n warnUnresolvedPlaceholders(prompt, profile.name)\n return prompt\n}\n\n/** 扫描 `{word}` 形式残留占位符(未被子代理替换逻辑消费的),warn 提示。 */\nfunction warnUnresolvedPlaceholders(prompt: string, profileName: string): void {\n const unresolved = new Set<string>()\n for (const match of prompt.matchAll(/\\{([a-z][a-z0-9_]*)\\}/gi)) {\n unresolved.add(match[0])\n }\n if (unresolved.size > 0) {\n console.warn(\n `[prompt] sub-agent profile \"${profileName}\" template has unresolved placeholder(s): ` +\n `${[...unresolved].join(', ')} — expected {task_title}/{task_description}/{task_files}. ` +\n 'The literal placeholder will reach the model; fix the template or extend the replacement map.',\n )\n }\n}\n\nexport function assembleGenericSubAgentPrompt(\n agentName: string,\n context: SubAgentPromptContext = {},\n): string {\n const replacements = {\n ...buildContextReplacements(context),\n '{agent_name}': agentName,\n }\n\n let prompt = GENERIC_SUBAGENT_TEMPLATE\n for (const [placeholder, value] of Object.entries(replacements)) {\n prompt = prompt.replaceAll(placeholder, value)\n }\n return prompt\n}\n\n/**\n * Find the matching profile from agent-profiles data.\n * Prefers exact match by name, then fuzzy match by capabilities.\n */\nexport function resolveAgentProfile(\n profiles: AgentProfile[],\n agentName: string,\n): AgentProfile | undefined {\n const exact = profiles.find((p) => p.name === agentName)\n if (exact) {\n return exact\n }\n\n const lower = agentName.toLowerCase()\n return profiles.find(\n (p) =>\n lower.endsWith(p.name.toLowerCase()) ||\n p.capabilities.some((c) => lower.includes(c.toLowerCase())) ||\n p.taskTypes.some((t) => lower.includes(t.toLowerCase())),\n )\n}\n","/**\n * tool-gated-sections.ts\n *\n * RFC-101:lead-guidance.md 按已解析能力键集合分段门控。\n *\n * markdown 内联标记 `<!-- requires-capability: X -->...<!-- /requires-capability -->` 标注段落对\n * 特定**能力**(非具体工具名)的依赖。`gateByToolAvailability` 在组装期扫描并剔除能力不可用的\n * 段落(含标记本身),消除悬空引用(如 minimal/standard 工具集会话看到引用了不存在能力的操作指引)。\n *\n * RFC-057 §3 D9 / M94-01:`packages/prompt` 是能力层,不得硬编码宿主工具名字面量——本模块与\n * `lead-guidance.md` 只认识语义能力键(如 `delegation`/`task-observability`),能力键到具体工具名\n * 的映射表下沉到宿主层(`@x-otto/coding`)。\n *\n * 纯函数,不访问全局状态、不做 I/O(RFC-101 重要事项规则 1)。\n */\n\n// 能力键字符集:字母/数字/连字符(如 `task-observability`),首字符不能是连字符,\n// 避免与标记结尾的 ` -->` 产生贪婪匹配歧义。\nconst CAPABILITY_NAME = '[a-zA-Z0-9][a-zA-Z0-9-]*'\nconst REQUIRES_CAPABILITY_PATTERN = new RegExp(\n `<!--\\\\s*requires-capability:\\\\s*(${CAPABILITY_NAME})\\\\s*-->([\\\\s\\\\S]*?)<!--\\\\s*/requires-capability\\\\s*-->`,\n 'g',\n)\nconst OPEN_TAG_PATTERN = new RegExp(`<!--\\\\s*requires-capability:\\\\s*(${CAPABILITY_NAME})\\\\s*-->`, 'g')\n\n/**\n * 扫描 `content` 中的 `requires-capability` 标记区块:\n * - `enabledCapabilities` 为 `undefined` → 不做任何剔除,但仍清理标记语法(标记本身不应泄漏到最终 prompt)。\n * - 标记的能力键在 `enabledCapabilities` 中 → 保留区块内容,剔除标记。\n * - 标记的能力键不在 `enabledCapabilities` 中 → 整段剔除(含内容与标记)。\n * - 未闭合标记(无匹配 `/requires-capability`)→ 保守处理:不剔除任何内容,原样保留(含标记本身),\n * 并触发 `onUnclosedTag`(RFC-101 重要事项规则 6:system prompt 组装失败是致命故障,必须优雅降级)。\n */\nexport function gateByToolAvailability(\n content: string,\n enabledCapabilities: ReadonlySet<string> | undefined,\n options?: {\n /** 已知能力键全集(用于检测标记拼写错误/能力已重命名)。缺省时不做未知能力键检测。 */\n knownCapabilities?: ReadonlySet<string>\n onUnknownCapability?: (capability: string) => void\n onUnclosedTag?: (capability: string) => void\n },\n): string {\n const gated = content.replace(REQUIRES_CAPABILITY_PATTERN, (_match, capability: string, body: string) => {\n if (options?.knownCapabilities && !options.knownCapabilities.has(capability)) {\n options.onUnknownCapability?.(capability)\n }\n\n if (!enabledCapabilities) {\n return body\n }\n\n return enabledCapabilities.has(capability) ? body : ''\n })\n\n if (options?.onUnclosedTag) {\n // 剩余的开标记(未被上面成对匹配消费掉)即未闭合——原样保留在 gated 中,仅上报观测。\n for (const match of gated.matchAll(OPEN_TAG_PATTERN)) {\n options.onUnclosedTag(match[1] ?? '')\n }\n }\n\n return gated\n}\n","import {\n assembleGenericSubAgentPrompt,\n assembleSubAgentPrompt,\n type AgentProfile,\n type SubAgentPromptContext,\n} from './sub-agent-prompt'\nimport { gateByToolAvailability } from './tool-gated-sections'\nimport type { PromptProvider } from './types'\n\nexport type PromptPreset = 'main' | 'subagent'\n\nexport type PromptPresetOptions =\n | {\n preset?: 'main'\n }\n | {\n preset: 'subagent'\n agentName: string\n profile?: AgentProfile\n context?: SubAgentPromptContext\n promptMode?: 'append' | 'replace'\n }\n\nconst LEAD_GUIDANCE_KEY = 'lead-guidance'\n\nexport interface PromptManagerOptions {\n provider: PromptProvider\n /**\n * 已知能力键全集(终局审查 2026-07-18 S2 接线):传入后 `assemble()` 对 `lead-guidance.md`\n * 中的 `requires-capability` 标记做拼写/漂移检测——标记的能力键不在此集合中时经\n * `onUnknownCapability` 告警(console.warn),防止\"能力键改名/写错 → 段落静默消失\"。\n * 真源是宿主层 `CAPABILITY_TOOL_MAP`(@x-otto/coding capability-tool-map.ts)的键集合,\n * 经组装根注入(本层不依赖 coding,保持 RFC-057 D9 能力层边界)。缺省不检测(向后兼容)。\n */\n knownCapabilities?: ReadonlySet<string>\n}\n\n/**\n * 结构化 prompt 组装产物。将 stable 前缀(进 prompt cache)与 volatile 尾段(cache 断点之后)\n * 分离,让下游不再字符串拼接而是按 lane 取用。\n *\n * - `stable`:会话内稳定的 system 前缀(lead-guidance.md 经能力门控后的内容)。\n * - `volatile`:逐轮可变的尾段。PromptManager 当前不产生 volatile 内容(动态段由\n * @x-otto/runtime 的 hook 体系注入),此字段为结构预留——未来 PromptManager 可注册\n * section 时自然填充。\n * - `render()`:拼接 stable + volatile 的便捷方法(向后兼容 assemblePreset 的 string 返回)。\n */\nexport interface PromptAssembly {\n readonly stable: string\n readonly volatile: string\n render(): string\n}\n\nexport class PromptManager {\n private readonly provider: PromptProvider\n private readonly knownCapabilities?: ReadonlySet<string>\n private leadGuidance: string | null = null\n\n constructor(options: PromptManagerOptions) {\n this.provider = options.provider\n this.knownCapabilities = options.knownCapabilities\n }\n\n async load(key: string): Promise<string | null> {\n const entry = await this.provider.load(key)\n return entry?.content ?? null\n }\n\n /**\n * 组装 lead-guidance.md 为结构化 PromptAssembly。\n *\n * `enabledCapabilities`:本会话已解析的能力键集合(RFC-101,见 `tool-gated-sections.ts`)。传入时\n * 对 `lead-guidance.md` 中 `<!-- requires-capability: X -->` 标记的段落做门控——`X` 不在集合中则\n * 剔除该段落,消除悬空引用。缺省(`undefined`)保持向后兼容:不剔除任何段落内容,仅清理标记语法本身。\n * 能力键本身不是工具名(RFC-057 D9/M94-01:能力层不得硬编码宿主工具名)——具体映射由宿主层\n * (`@x-otto/coding`)的 `CAPABILITY_TOOL_MAP` 负责,本层只消费已转换好的能力键集合。\n */\n async assemble(enabledCapabilities?: ReadonlySet<string>): Promise<PromptAssembly> {\n if (this.leadGuidance === null) {\n this.leadGuidance = (await this.load(LEAD_GUIDANCE_KEY)) ?? ''\n }\n const stable = gateByToolAvailability(this.leadGuidance, enabledCapabilities, {\n knownCapabilities: this.knownCapabilities,\n onUnknownCapability: (capability) =>\n console.warn(\n `[prompt] lead-guidance.md references unknown capability \"${capability}\" ` +\n '(typo or renamed key in CAPABILITY_TOOL_MAP?) — the gated section is silently dropped',\n ),\n onUnclosedTag: (capability) =>\n console.warn(`[prompt] lead-guidance.md has an unclosed requires-capability tag: \"${capability}\"`),\n })\n return {\n stable,\n volatile: '',\n render: () => stable,\n }\n }\n\n async assemblePreset(\n options: PromptPresetOptions = { preset: 'main' },\n enabledCapabilities?: ReadonlySet<string>,\n ): Promise<string> {\n if (options.preset === 'subagent') {\n if (!options.profile) {\n return assembleGenericSubAgentPrompt(options.agentName, options.context)\n }\n if (options.profile.promptMode === 'append') {\n return (await this.assemble(enabledCapabilities)).render()\n }\n return assembleSubAgentPrompt(options.profile, options.context)\n }\n\n return (await this.assemble(enabledCapabilities)).render()\n }\n\n /**\n * append 模式子代理的**角色块** —— 渲染后的 profile 模板,由宿主注入为\n * **volatile system 尾段**(落在 prompt cache 断点之后)。append 的 system prompt 主体由\n * `assemblePreset` 返回基底(共享、进缓存),角色差异走此尾段——既得专门化又不击穿跨子代理缓存。\n * 仅 append 模式返回值;replace/缺省(模板已是 system prompt 主体)/无 profile 返回 undefined。\n */\n assembleSubAgentTail(options: PromptPresetOptions): string | undefined {\n if (options.preset !== 'subagent') {\n return undefined\n }\n if (options.profile && options.profile.promptMode === 'append') {\n return assembleSubAgentPrompt(options.profile, options.context)\n }\n return undefined\n }\n\n async loadRuntimeLessonsTemplate(): Promise<string | null> {\n return this.load('lesson/runtime-lessons')\n }\n}\n","import { resolve, dirname } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n\nexport const BUILTIN_PROMPTS_DIR = resolve(__dirname, '..', 'prompts')\n","import type { Lesson } from './types'\n\nexport function buildLessonInjection(lessons: Lesson[], template?: string): string {\n if (lessons.length === 0) {\n return ''\n }\n\n const lines = lessons.map(\n (l, i) => `${i + 1}. [${l.tags.join(', ')}] ${l.trigger} → ${l.insight}`,\n )\n\n const t =\n template ??\n `### Runtime Lessons\nThe following lessons were learned from previous sessions:\n{lessons}`\n return t.replace('{lessons}', lines.join('\\n'))\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/prompt",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0-alpha.10",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist",
|
|
6
6
|
"prompts"
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
"registry": "https://registry.npmjs.org",
|
|
20
20
|
"tag": "alpha"
|
|
21
21
|
},
|
|
22
|
-
"dependencies": {
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@x-otto/agent": "0.1.0-alpha.10"
|
|
24
|
+
},
|
|
23
25
|
"private": false,
|
|
24
26
|
"scripts": {
|
|
25
27
|
"build": "tsdown",
|
package/prompts/lead-guidance.md
CHANGED
|
@@ -89,6 +89,7 @@ If you encounter unexpected state (unfamiliar files, uncommitted changes you did
|
|
|
89
89
|
|
|
90
90
|
- Be concise and direct; lead with the answer or action, not the reasoning. One sentence beats three; skip transitions, restatements, filler. When you perform an action, briefly confirm what was done rather than explaining what you plan to do.
|
|
91
91
|
- Use fenced code blocks with language identifiers when showing code. Reference files relative to project root, `path:line` form (e.g. `src/app.ts:42`) when pointing at a location.
|
|
92
|
+
- Use tables only for short enumerable facts (file names, line numbers, pass/fail status); explain reasoning in prose before or after, never packed into table cells.
|
|
92
93
|
- No emojis unless explicitly requested. No horizontal rules (`---`) or decorative separators.
|
|
93
94
|
|
|
94
95
|
#### Error Recovery
|
|
@@ -210,11 +211,13 @@ Treat completion as **unproven until verified against the actual current state**
|
|
|
210
211
|
|
|
211
212
|
### Budget & Wrap-up
|
|
212
213
|
|
|
213
|
-
Your work operates under budgets (
|
|
214
|
+
Your work operates under budgets (tool-call turns, session cost) and a finite context window. Distinguish three different signals — they are not the same:
|
|
214
215
|
|
|
215
|
-
- **
|
|
216
|
-
- **
|
|
217
|
-
- **
|
|
216
|
+
- **Context near the window limit**: the engine automatically compacts older history and continues — treat a compaction boundary like any other turn (see System-Generated Context). Never wrap up or stop merely because the context grew large or a `session tokens used` counter is high. A large cumulative token count is not a reason to stop an unfinished task.
|
|
217
|
+
- **Tool-turn / cost counters** (e.g. `<engine_budget dimension="tokens">` stating used/budget output tokens): a bounded-resource signal — prefer finishing and verifying existing work over opening new lines of work that cannot complete within the remaining budget. This bounds *scope expansion*, not task completion.
|
|
218
|
+
- **Todo continuation rounds** (e.g. `<engine_continuation round="2" cap="12">`): the engine auto-continues unfinished todos after your turn. round/cap is a soft admission budget — it bounds automatic continuation, not task completion. Do not stop early because rounds look scarce; if work genuinely remains, keep the todos active and the engine will admit another round. No token numbers appear in this envelope by design.
|
|
219
|
+
- **Explicit wrap-up instruction**: do not start new tool work. Summarize concrete progress, list what remains or is blocked (convert to tracked todos), give a clear next step. A clean remainder list is a successful stop, not a failure.
|
|
220
|
+
- **Never** mark work complete merely because a budget ran out — report the true state instead.
|
|
218
221
|
|
|
219
222
|
<!-- requires-capability: delegation -->
|
|
220
223
|
### Model Slot Guidance
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
Capability triage — when a request feels hard to fulfill, classify it first:
|
|
2
|
+
|
|
3
|
+
- You lack a TOOL or integration that would be needed → use the capability-gap reporting tool (name injected host-side).
|
|
4
|
+
- You have everything needed, but you notice you've repeated the same multi-step routine
|
|
5
|
+
many times in this project → nothing to do; otto observes repeated routines in the
|
|
6
|
+
background and will offer to save one as a reusable skill.
|
|
7
|
+
- Anything else → just do the task.
|
|
8
|
+
|
|
9
|
+
Do not announce this triage or narrate which branch applied.
|