@4fu/arbor-prompt 2026.1107.0 → 2026.1109.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/package.json +2 -2
- package/render.js +8 -3
- package/system.d.ts +20 -7
- package/system.js +22 -9
- package/text.d.ts +7 -0
- package/text.js +11 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@4fu/arbor-prompt",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.1109.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Typed prompt blocks, the one deterministic renderer, the replaceable Chinese text table and the system prompt sections.",
|
|
6
6
|
"exports": {
|
|
@@ -49,6 +49,6 @@
|
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@4fu/arbor-errors": "2026.1107.0",
|
|
52
|
-
"@4fu/arbor-interfaces": "2026.
|
|
52
|
+
"@4fu/arbor-interfaces": "2026.1109.0"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/render.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - the output is deterministic, so the same blocks always give the same bytes.
|
|
11
11
|
*/
|
|
12
12
|
import { BUILTIN_BLOCK_KINDS } from "./blocks.js";
|
|
13
|
-
import { formatText, zhTextTable } from "./text.js";
|
|
13
|
+
import { formatText, zhTextTable, PLACEHOLDER } from "./text.js";
|
|
14
14
|
/** Joins refs, which are identifiers. */
|
|
15
15
|
const REF_SEPARATOR = ", ";
|
|
16
16
|
/** Joins a list of words inside one sentence. */
|
|
@@ -169,15 +169,20 @@ export class Renderer {
|
|
|
169
169
|
const template = this.table[resolved];
|
|
170
170
|
if (template === undefined)
|
|
171
171
|
return undefined;
|
|
172
|
-
for (const [, name] of `${key} ${template}`.matchAll(
|
|
172
|
+
for (const [, name] of `${key} ${template}`.matchAll(PLACEHOLDER)) {
|
|
173
173
|
if (name === undefined)
|
|
174
174
|
continue;
|
|
175
175
|
if (args[name] === undefined || args[name] === "")
|
|
176
176
|
return undefined;
|
|
177
177
|
carried.add(name);
|
|
178
178
|
}
|
|
179
|
+
// The line is complete when every placeholder its own template names has a
|
|
180
|
+
// value - never by scanning what was filled: a value that carries a `{` of
|
|
181
|
+
// its own, JSON in a message or a snippet in a note, is data the line must
|
|
182
|
+
// keep, not a placeholder it must drop for
|
|
183
|
+
// (docs/prompting.md#blocks-and-the-renderer).
|
|
179
184
|
const filled = formatText(template, args);
|
|
180
|
-
if (filled.
|
|
185
|
+
if (filled.trim() === "")
|
|
181
186
|
return undefined;
|
|
182
187
|
return filled;
|
|
183
188
|
}
|
package/system.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* with the snapshot epoch, and rendering is deterministic: no timestamps or
|
|
7
7
|
* counters reach it.
|
|
8
8
|
*/
|
|
9
|
-
import type { DirectToolSpec, EnvItem, ModuleSpec, PromptSection, Settings } from "@4fu/arbor-interfaces";
|
|
9
|
+
import type { DirectToolSpec, EnvItem, ModuleSpec, PromptSection, Settings, WindowRef } from "@4fu/arbor-interfaces";
|
|
10
10
|
import { Renderer } from "./render.ts";
|
|
11
11
|
/** What one epoch's system prompt is built from. */
|
|
12
12
|
export interface SystemPromptInput {
|
|
@@ -55,17 +55,30 @@ export declare function sectionRevision(section: PromptSection): Promise<string>
|
|
|
55
55
|
* One plugin's prompt section, rendered in its tag, with what it produced. The
|
|
56
56
|
* revision it reports is what a later call compares against, so an unchanged
|
|
57
57
|
* section is never rendered twice.
|
|
58
|
+
*
|
|
59
|
+
* `window` is the window this render is for: a section that serves several
|
|
60
|
+
* sessions answers for that one, and a section whose content is the same
|
|
61
|
+
* everywhere renders the same text for each
|
|
62
|
+
* (docs/prompting.md#plugin-sections-that-change). `label` is the opaque label
|
|
63
|
+
* the embedding attached to what that window serves, carried through
|
|
64
|
+
* (docs/windows.md#what-a-window-serves).
|
|
58
65
|
*/
|
|
59
|
-
export declare function renderPluginSection(section: PromptSection, renderer: Renderer): Promise<RenderedPluginSection>;
|
|
66
|
+
export declare function renderPluginSection(section: PromptSection, renderer: Renderer, window?: WindowRef, label?: string): Promise<RenderedPluginSection>;
|
|
60
67
|
/**
|
|
61
|
-
* Every plugin section, rendered once, in the order they registered
|
|
62
|
-
*
|
|
63
|
-
* later call can tell a section that
|
|
68
|
+
* Every plugin section, rendered once, in the order they registered, for the
|
|
69
|
+
* window they are rendered for. A section whose content renders to nothing is
|
|
70
|
+
* still listed - with empty text - so a later call can tell a section that
|
|
71
|
+
* stayed empty from one that appeared.
|
|
64
72
|
*/
|
|
65
|
-
export declare function renderPluginSections(plugins: readonly PromptSection[], renderer: Renderer): Promise<readonly RenderedPluginSection[]>;
|
|
73
|
+
export declare function renderPluginSections(plugins: readonly PromptSection[], renderer: Renderer, window?: WindowRef, label?: string): Promise<readonly RenderedPluginSection[]>;
|
|
66
74
|
/**
|
|
67
75
|
* Build the frozen prefix. `plugins` are rendered first because their content
|
|
68
76
|
* is produced per epoch, their sections render in a tag named after the plugin,
|
|
69
77
|
* and one of them being outside is what puts the precedence rule into `<agent>`.
|
|
78
|
+
*
|
|
79
|
+
* `window` is the window the prefix is built for, when it is built for one: a
|
|
80
|
+
* section that renders per window makes this prefix that window's own. `label`
|
|
81
|
+
* is the opaque label the embedding attached to what that window serves, which
|
|
82
|
+
* a per-window section reads (docs/windows.md#what-a-window-serves).
|
|
70
83
|
*/
|
|
71
|
-
export declare function renderSystemPrompt(input: SystemPromptInput, renderer: Renderer): Promise<RenderedSystemPrompt>;
|
|
84
|
+
export declare function renderSystemPrompt(input: SystemPromptInput, renderer: Renderer, window?: WindowRef, label?: string): Promise<RenderedSystemPrompt>;
|
package/system.js
CHANGED
|
@@ -93,9 +93,16 @@ function outsideBlock(content) {
|
|
|
93
93
|
* One plugin's prompt section, rendered in its tag, with what it produced. The
|
|
94
94
|
* revision it reports is what a later call compares against, so an unchanged
|
|
95
95
|
* section is never rendered twice.
|
|
96
|
+
*
|
|
97
|
+
* `window` is the window this render is for: a section that serves several
|
|
98
|
+
* sessions answers for that one, and a section whose content is the same
|
|
99
|
+
* everywhere renders the same text for each
|
|
100
|
+
* (docs/prompting.md#plugin-sections-that-change). `label` is the opaque label
|
|
101
|
+
* the embedding attached to what that window serves, carried through
|
|
102
|
+
* (docs/windows.md#what-a-window-serves).
|
|
96
103
|
*/
|
|
97
|
-
export async function renderPluginSection(section, renderer) {
|
|
98
|
-
const content = await section.content();
|
|
104
|
+
export async function renderPluginSection(section, renderer, window, label) {
|
|
105
|
+
const content = await section.content(window, label);
|
|
99
106
|
return {
|
|
100
107
|
tag: section.tag,
|
|
101
108
|
text: renderer.render(section.outside === true ? [outsideBlock(content)] : content),
|
|
@@ -104,24 +111,30 @@ export async function renderPluginSection(section, renderer) {
|
|
|
104
111
|
};
|
|
105
112
|
}
|
|
106
113
|
/**
|
|
107
|
-
* Every plugin section, rendered once, in the order they registered
|
|
108
|
-
*
|
|
109
|
-
* later call can tell a section that
|
|
114
|
+
* Every plugin section, rendered once, in the order they registered, for the
|
|
115
|
+
* window they are rendered for. A section whose content renders to nothing is
|
|
116
|
+
* still listed - with empty text - so a later call can tell a section that
|
|
117
|
+
* stayed empty from one that appeared.
|
|
110
118
|
*/
|
|
111
|
-
export async function renderPluginSections(plugins, renderer) {
|
|
119
|
+
export async function renderPluginSections(plugins, renderer, window, label) {
|
|
112
120
|
const rendered = [];
|
|
113
121
|
for (const plugin of plugins)
|
|
114
|
-
rendered.push(await renderPluginSection(plugin, renderer));
|
|
122
|
+
rendered.push(await renderPluginSection(plugin, renderer, window, label));
|
|
115
123
|
return rendered;
|
|
116
124
|
}
|
|
117
125
|
/**
|
|
118
126
|
* Build the frozen prefix. `plugins` are rendered first because their content
|
|
119
127
|
* is produced per epoch, their sections render in a tag named after the plugin,
|
|
120
128
|
* and one of them being outside is what puts the precedence rule into `<agent>`.
|
|
129
|
+
*
|
|
130
|
+
* `window` is the window the prefix is built for, when it is built for one: a
|
|
131
|
+
* section that renders per window makes this prefix that window's own. `label`
|
|
132
|
+
* is the opaque label the embedding attached to what that window serves, which
|
|
133
|
+
* a per-window section reads (docs/windows.md#what-a-window-serves).
|
|
121
134
|
*/
|
|
122
|
-
export async function renderSystemPrompt(input, renderer) {
|
|
135
|
+
export async function renderSystemPrompt(input, renderer, window, label) {
|
|
123
136
|
const table = renderer.table;
|
|
124
|
-
const sections = await renderPluginSections(input.plugins ?? [], renderer);
|
|
137
|
+
const sections = await renderPluginSections(input.plugins ?? [], renderer, window, label);
|
|
125
138
|
const parts = sectionsOf(input, table, sections.some((section) => section.outside)).map((section) => [`<${section.tag}>`, ...section.lines, `</${section.tag}>`].join("\n"));
|
|
126
139
|
for (const section of sections) {
|
|
127
140
|
if (section.text === "")
|
package/text.d.ts
CHANGED
|
@@ -11,6 +11,13 @@ import type { TextTable } from "@4fu/arbor-interfaces";
|
|
|
11
11
|
export type TextArgs = Record<string, string | number>;
|
|
12
12
|
/** The framework's own table: Chinese, one entry per fixed string. */
|
|
13
13
|
export declare const zhTextTable: TextTable;
|
|
14
|
+
/**
|
|
15
|
+
* One placeholder in a table entry: `{name}`. It is the whole vocabulary of
|
|
16
|
+
* what a renderer fills, so anything else inside braces - `{ everyMs }` written
|
|
17
|
+
* with spaces, a `{` a filled value carries - is literal text and stays as it
|
|
18
|
+
* was written.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PLACEHOLDER: RegExp;
|
|
14
21
|
/** Fill one table entry. Unknown placeholders are left as they are. */
|
|
15
22
|
export declare function formatText(template: string, args?: TextArgs): string;
|
|
16
23
|
/**
|
package/text.js
CHANGED
|
@@ -94,7 +94,7 @@ export const zhTextTable = {
|
|
|
94
94
|
"continuation.open_work": "上个窗口链未完成的工作:{openWork};按需继续或收尾,状态写进笔记和交接内容。",
|
|
95
95
|
// ---- results and refusals the tools report -----------------------------
|
|
96
96
|
"secret.hidden": "[hidden:{name}]",
|
|
97
|
-
"result.sent": "已记录 {ref}
|
|
97
|
+
"result.sent": "已记录 {ref},效果已入队;执行结果以效果结果事件为准。",
|
|
98
98
|
"result.effect.ok": "效果 {ref} 已执行。",
|
|
99
99
|
"result.effect.failed": "效果 {ref} 没有执行({reason})。",
|
|
100
100
|
"result.window_message": "消息 {ref} 已发送给 {window}。",
|
|
@@ -168,6 +168,7 @@ export const zhTextTable = {
|
|
|
168
168
|
"refused.send.target": "发送目标不能为空。",
|
|
169
169
|
"refused.send.content": "发送内容不能为空。",
|
|
170
170
|
"refused.send.secret": "发送内容包含环境变量 {name} 的原始值,本次没有发送。",
|
|
171
|
+
"refused.send.unowned": "没有效果执行器负责目标 {target},本次没有发送。",
|
|
171
172
|
"refused.bash.notfound": "没有作业 {job}。",
|
|
172
173
|
"refused.note.write.journal": "笔记 {ref} 没有写入:记录拒绝了这次事件({detail})。",
|
|
173
174
|
"refused.note.write.title": "笔记需要一个标题。",
|
|
@@ -234,6 +235,7 @@ export const zhTextTable = {
|
|
|
234
235
|
"next.send.target": "目标写成一个引用或一个外部 id。",
|
|
235
236
|
"next.send.content": "补上要发送的内容。",
|
|
236
237
|
"next.send.secret": "请删去其中涉及该值的内容,或只写变量名 {name} 指代,然后重发。",
|
|
238
|
+
"next.send.unowned": "用 channels.list 查看已接入的频道,把内容发到有频道负责的目标。",
|
|
237
239
|
"next.bash.notfound": "用 bash 重新执行该命令;本窗口不会收到后台作业的完成通知,需要结果时用 bash_output 查询。",
|
|
238
240
|
"next.note.write.journal": "修正记录拒绝的原因,然后重新写入。",
|
|
239
241
|
"next.note.write.title": "补上一个简短标题。",
|
|
@@ -377,11 +379,18 @@ export const zhTextTable = {
|
|
|
377
379
|
"function.services.status": "查看一个服务的状态:版本、端口、重启次数和最近错误。",
|
|
378
380
|
"function.services.logs": "读取一个服务的日志,可按行数取尾部。",
|
|
379
381
|
};
|
|
382
|
+
/**
|
|
383
|
+
* One placeholder in a table entry: `{name}`. It is the whole vocabulary of
|
|
384
|
+
* what a renderer fills, so anything else inside braces - `{ everyMs }` written
|
|
385
|
+
* with spaces, a `{` a filled value carries - is literal text and stays as it
|
|
386
|
+
* was written.
|
|
387
|
+
*/
|
|
388
|
+
export const PLACEHOLDER = /\{(\w+)\}/g;
|
|
380
389
|
/** Fill one table entry. Unknown placeholders are left as they are. */
|
|
381
390
|
export function formatText(template, args) {
|
|
382
391
|
if (args === undefined)
|
|
383
392
|
return template;
|
|
384
|
-
return template.replace(
|
|
393
|
+
return template.replace(PLACEHOLDER, (match, key) => {
|
|
385
394
|
const value = args[key];
|
|
386
395
|
return value === undefined ? match : String(value);
|
|
387
396
|
});
|