@xingular/harness 1.2.0 → 1.3.1-unstable.1
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/CHANGELOG.md +63 -0
- package/README.md +26 -17
- package/builtin/skills/plugin-authoring/SKILL.md +34 -0
- package/dist/cli.js +7362 -6239
- package/dist/sdk/agents/builtin.js +3 -3
- package/dist/sdk/agents/planResearch.d.ts +23 -1
- package/dist/sdk/agents/planResearch.js +71 -8
- package/dist/sdk/agents/profiles.d.ts +16 -4
- package/dist/sdk/agents/profiles.js +75 -12
- package/dist/sdk/annotations/bridge.d.ts +2 -0
- package/dist/sdk/annotations/bridge.js +4 -0
- package/dist/sdk/annotations/capture.d.ts +44 -2
- package/dist/sdk/annotations/capture.js +104 -3
- package/dist/sdk/annotations/delivery.d.ts +24 -17
- package/dist/sdk/annotations/delivery.js +82 -49
- package/dist/sdk/annotations/inspect.js +7 -6
- package/dist/sdk/annotations/pageScript.js +4 -4
- package/dist/sdk/annotations/part.d.ts +0 -2
- package/dist/sdk/annotations/part.js +4 -33
- package/dist/sdk/annotations/queue.d.ts +2 -2
- package/dist/sdk/annotations/queue.js +12 -7
- package/dist/sdk/annotations/serverMethods.js +11 -7
- package/dist/sdk/annotations/store.d.ts +13 -21
- package/dist/sdk/annotations/store.js +47 -37
- package/dist/sdk/capabilities/runSnapshot.js +10 -4
- package/dist/sdk/commands/list.js +2 -0
- package/dist/sdk/commands/registry.d.ts +11 -3
- package/dist/sdk/commands/registry.js +3 -2
- package/dist/sdk/core/links.d.ts +51 -0
- package/dist/sdk/core/links.js +155 -0
- package/dist/sdk/core/shell.d.ts +20 -0
- package/dist/sdk/core/shell.js +73 -0
- package/dist/sdk/core/workspaceBashTool.d.ts +17 -1
- package/dist/sdk/core/workspaceBashTool.js +16 -2
- package/dist/sdk/core/workspaceSandbox.d.ts +10 -0
- package/dist/sdk/core/workspaceSandbox.js +52 -12
- package/dist/sdk/core/workspaceWriteOperations.d.ts +11 -0
- package/dist/sdk/core/workspaceWriteOperations.js +26 -8
- package/dist/sdk/doctor/checks.js +71 -12
- package/dist/sdk/harness.d.ts +5 -0
- package/dist/sdk/harness.js +11 -6
- package/dist/sdk/hub/hub.js +5 -3
- package/dist/sdk/index.d.ts +3 -2
- package/dist/sdk/index.js +2 -1
- package/dist/sdk/plugins/builtin.js +14 -9
- package/dist/sdk/plugins/builtinRegistry.d.ts +3 -1
- package/dist/sdk/plugins/builtinRegistry.js +9 -3
- package/dist/sdk/plugins/discover.d.ts +7 -1
- package/dist/sdk/plugins/discover.js +41 -2
- package/dist/sdk/plugins/identity.js +1 -1
- package/dist/sdk/plugins/install.d.ts +6 -2
- package/dist/sdk/plugins/install.js +29 -5
- package/dist/sdk/plugins/inventory.js +3 -3
- package/dist/sdk/plugins/manifest.d.ts +2 -0
- package/dist/sdk/plugins/manifest.js +21 -4
- package/dist/sdk/plugins/plugins.d.ts +2 -0
- package/dist/sdk/plugins/plugins.js +1 -0
- package/dist/sdk/plugins/proposals.d.ts +17 -0
- package/dist/sdk/plugins/proposals.js +147 -0
- package/dist/sdk/plugins/types.d.ts +1 -1
- package/dist/sdk/plugins/types.js +1 -1
- package/dist/sdk/prompt/compile.d.ts +13 -4
- package/dist/sdk/prompt/compile.js +46 -20
- package/dist/sdk/prompt/standard.d.ts +1 -1
- package/dist/sdk/prompt/standard.js +1 -1
- package/dist/sdk/server/methods.js +14 -2
- package/dist/sdk/sessions/compactionCheckpoint.d.ts +3 -3
- package/dist/sdk/sessions/compactionCheckpoint.js +12 -12
- package/dist/sdk/sessions/continuation.d.ts +4 -2
- package/dist/sdk/sessions/continuation.js +7 -6
- package/dist/sdk/sessions/contracts.d.ts +22 -4
- package/dist/sdk/sessions/contracts.js +12 -7
- package/dist/sdk/sessions/planMode.js +2 -2
- package/dist/sdk/sessions/runStore.js +3 -1
- package/dist/sdk/sessions/runner.js +2 -2
- package/dist/sdk/sessions/sessionState.d.ts +13 -0
- package/dist/sdk/sessions/sessionState.js +31 -0
- package/dist/sdk/sessions/sessions.d.ts +7 -0
- package/dist/sdk/sessions/sessions.js +73 -15
- package/dist/sdk/sessions/subagentService.d.ts +15 -0
- package/dist/sdk/sessions/subagentService.js +195 -55
- package/dist/sdk/sessions/subagents.d.ts +10 -1
- package/dist/sdk/sessions/subagents.js +4 -0
- package/dist/sdk/sessions/workSlice.d.ts +8 -5
- package/dist/sdk/sessions/workSlice.js +77 -45
- package/dist/sdk/terminal/controller.d.ts +20 -0
- package/dist/sdk/terminal/controller.js +43 -18
- package/dist/sdk/terminal/opentui/ansi.d.ts +25 -0
- package/dist/sdk/terminal/opentui/ansi.js +192 -0
- package/dist/sdk/terminal/opentui/model.d.ts +163 -0
- package/dist/sdk/terminal/opentui/model.js +482 -0
- package/dist/sdk/terminal/opentui/subagentsWidget.d.ts +3 -3
- package/dist/sdk/terminal/opentui/subagentsWidget.js +7 -5
- package/dist/sdk/terminal/opentui/theme.d.ts +2 -0
- package/dist/sdk/terminal/opentui/theme.js +2 -0
- package/dist/sdk/terminal/piExtension.js +98 -15
- package/dist/sdk/terminal/subagentMonitor.d.ts +17 -4
- package/dist/sdk/terminal/subagentMonitor.js +29 -8
- package/dist/sdk/terminal/terminals.d.ts +16 -0
- package/dist/sdk/terminal/terminals.js +130 -26
- package/dist/sdk/terminal/trajectory.d.ts +6 -1
- package/dist/sdk/terminal/trajectory.js +8 -0
- package/dist/sdk/terminal/tui/annotateCommand.d.ts +5 -1
- package/dist/sdk/terminal/tui/annotateCommand.js +21 -7
- package/dist/sdk/terminal/tui/annotateSend.d.ts +30 -0
- package/dist/sdk/terminal/tui/annotateSend.js +40 -0
- package/dist/sdk/terminal/tui/chrome.d.ts +4 -0
- package/dist/sdk/terminal/tui/chrome.js +13 -0
- package/dist/sdk/terminal/tui/commandPages.d.ts +1 -1
- package/dist/sdk/terminal/tui/commandPages.js +16 -11
- package/dist/sdk/terminal/tui/commands.d.ts +4 -0
- package/dist/sdk/terminal/tui/commands.js +12 -2
- package/dist/sdk/terminal/tui/dialogs.js +1 -0
- package/dist/sdk/terminal/tui/pageOverlay.d.ts +3 -0
- package/dist/sdk/terminal/tui/pageOverlay.js +25 -7
- package/dist/sdk/terminal/tui/pages/agentsPage.js +5 -2
- package/dist/sdk/terminal/tui/pages/annotatePage.d.ts +70 -16
- package/dist/sdk/terminal/tui/pages/annotatePage.js +349 -115
- package/dist/sdk/terminal/tui/pages/browserPage.js +5 -4
- package/dist/sdk/terminal/tui/pages/hubPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/hubPage.js +192 -28
- package/dist/sdk/terminal/tui/pages/pluginsPage.d.ts +14 -1
- package/dist/sdk/terminal/tui/pages/pluginsPage.js +295 -25
- package/dist/sdk/terminal/tui/pages/subagentsPage.js +8 -5
- package/dist/sdk/terminal/tui/pages/usagePage.d.ts +4 -2
- package/dist/sdk/terminal/tui/pages/usagePage.js +39 -12
- package/dist/sdk/terminal/tui/pluginCommands.d.ts +1 -1
- package/dist/sdk/terminal/tui/pluginCommands.js +13 -3
- package/dist/sdk/tools/defaultSkills.d.ts +4 -0
- package/dist/sdk/tools/defaultSkills.js +20 -0
- package/dist/sdk/tools/definitions.d.ts +3 -1
- package/dist/sdk/tools/definitions.js +17 -1
- package/dist/sdk/tools/dispatch.js +12 -0
- package/dist/sdk/tools/harnessTools.js +1 -1
- package/dist/sdk/tools/sessionName.d.ts +9 -0
- package/dist/sdk/tools/sessionName.js +58 -11
- package/dist/sdk/tools/spawnSubagent.js +9 -7
- package/dist/sdk/tools/todo.d.ts +15 -0
- package/dist/sdk/tools/todo.js +64 -0
- package/dist/sdk/world/world.d.ts +0 -2
- package/dist/terminal-opentui.js +3331 -1584
- package/dist/terminal-pi.js +421 -133
- package/docs/README.md +3 -0
- package/docs/architecture/00-overview.md +1 -1
- package/docs/architecture/01-extension-points.md +1 -1
- package/docs/architecture/02-sessions-and-runs.md +73 -36
- package/docs/architecture/03-variants.md +1 -3
- package/docs/architecture/04-prompt.md +24 -13
- package/docs/architecture/06-plugins-and-capabilities.md +46 -10
- package/docs/architecture/07-tools-and-gate.md +49 -8
- package/docs/architecture/09-accounts-and-connectors.md +8 -2
- package/docs/architecture/10-server-protocol.md +68 -16
- package/docs/architecture/11-terminal.md +94 -45
- package/docs/architecture/12-storage.md +1 -0
- package/docs/architecture/13-world-tools.md +8 -6
- package/docs/architecture/14-surfaces-and-parity.md +4 -1
- package/docs/architecture/README.md +20 -18
- package/docs/architecture/proposals/PROP-0006-run-checklist.md +2 -0
- package/docs/architecture/proposals/PROP-0018-annotation-queue-and-app-targets.md +6 -2
- package/docs/architecture/proposals/PROP-0041-bounded-work-slices.md +32 -21
- package/docs/architecture/proposals/PROP-0044-work-slice-defaults.md +93 -0
- package/docs/architecture/proposals/PROP-0045-rename-checklist-to-todo.md +132 -0
- package/docs/architecture/proposals/PROP-0046-progress-aware-stall-guards.md +82 -0
- package/docs/architecture/proposals/PROP-0047-plugins-hub-separation.md +122 -0
- package/docs/architecture/proposals/PROP-0048-simple-annotate.md +85 -0
- package/docs/architecture/proposals/README.md +11 -6
- package/docs/decisions/ADR-0039-progress-aware-stall-guards.md +39 -0
- package/docs/decisions/ADR-0040-architecture-change-workflow.md +31 -0
- package/docs/decisions/ADR-0041-plugins-hub-separation.md +41 -0
- package/docs/decisions/ADR-0042-windows-shell-without-sandbox.md +40 -0
- package/docs/decisions/ADR-0043-parents-continue-stopped-subagents.md +49 -0
- package/docs/decisions/ADR-0044-annotations-pending-or-sent.md +24 -0
- package/docs/decisions/ADR-0045-default-plugin-authoring-skill.md +9 -0
- package/docs/decisions/ADR-0046-hash-bound-plugin-proposals.md +20 -0
- package/docs/decisions/ADR-0047-links-open-with-the-os-handler.md +47 -0
- package/docs/doctor.md +14 -1
- package/docs/parity.md +16 -13
- package/docs/releases/1.3.0.md +21 -0
- package/docs/releases/1.3.1-unstable.1.md +8 -0
- package/docs/releases/README.md +9 -6
- package/docs/terminal-tui.md +127 -65
- package/package.json +1 -1
- package/dist/sdk/terminal/tui/pages/notesPage.d.ts +0 -75
- package/dist/sdk/terminal/tui/pages/notesPage.js +0 -201
- package/dist/sdk/tools/checklist.d.ts +0 -9
- package/dist/sdk/tools/checklist.js +0 -55
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { BUILTIN_PLUGIN_REGISTRY } from "../../plugins/builtin.js";
|
|
2
2
|
import { parseAnnotate } from "./annotateCommand.js";
|
|
3
|
+
import { sendUnsent } from "./annotateSend.js";
|
|
3
4
|
/** Terminal adapters for commands declared by Harness-owned feature plugins. */
|
|
4
5
|
export function createBuiltinPluginCommandHandlers(host, openPage) {
|
|
5
6
|
const handlers = new Map([
|
|
6
|
-
['annotations:
|
|
7
|
-
['annotations:annotate', async (argument) => {
|
|
7
|
+
['annotations:open', async (argument) => {
|
|
8
8
|
const start = parseAnnotate(argument);
|
|
9
9
|
if (typeof start === 'string')
|
|
10
10
|
host.ui.notify(start, 'error');
|
|
11
11
|
else
|
|
12
12
|
await openPage('annotate', { annotate: start });
|
|
13
13
|
}],
|
|
14
|
+
// The All annotations tab; no capture window opens until the person asks for one.
|
|
15
|
+
['annotations:notes', async () => openPage('notes')],
|
|
16
|
+
['annotations:submit', async (argument) => {
|
|
17
|
+
host.ui.notify(await sendUnsent(host.control, host.ui, { prompt: argument.trim() }), 'info');
|
|
18
|
+
}],
|
|
19
|
+
['annotations:clear', async () => {
|
|
20
|
+
const { notes } = await host.control('terminal.annotations.clear');
|
|
21
|
+
host.ui.notify(notes.length ? `Resolved ${notes.length} unsent annotation${notes.length === 1 ? '' : 's'}; the agent can no longer read ${notes.length === 1 ? 'it' : 'them'}. Reopen from /annotate:notes.` : 'No unsent annotations to clear.', 'info');
|
|
22
|
+
}],
|
|
14
23
|
['usage:analysis', async (argument) => {
|
|
15
24
|
const period = argument.trim().toLowerCase();
|
|
16
25
|
if (period && !['today', '7d', '30d', 'all'].includes(period)) {
|
|
@@ -19,7 +28,8 @@ export function createBuiltinPluginCommandHandlers(host, openPage) {
|
|
|
19
28
|
}
|
|
20
29
|
await openPage('usage', { usageTab: 'analysis', ...(period ? { usagePeriod: period } : {}) });
|
|
21
30
|
}],
|
|
22
|
-
['usage:limit', async () => openPage('usage', { usageTab: 'limit' })]
|
|
31
|
+
['usage:limit', async () => openPage('usage', { usageTab: 'limit' })],
|
|
32
|
+
['usage:accounts', async () => openPage('usage', { usageTab: 'accounts' })]
|
|
23
33
|
]);
|
|
24
34
|
const commands = BUILTIN_PLUGIN_REGISTRY.commands();
|
|
25
35
|
for (const command of commands) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Locate release-owned resources from source, the SDK, or a bundled executable. */
|
|
2
|
+
export declare function defaultSkillPath(): string;
|
|
3
|
+
/** Variant skills win on a name collision, without modifying a pinned variant. */
|
|
4
|
+
export declare function defaultSkillPaths(variantDir: string | null | undefined): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
/** Locate release-owned resources from source, the SDK, or a bundled executable. */
|
|
5
|
+
export function defaultSkillPath() {
|
|
6
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
for (;;) {
|
|
8
|
+
const path = join(dir, 'builtin', 'skills', 'plugin-authoring', 'SKILL.md');
|
|
9
|
+
if (existsSync(path))
|
|
10
|
+
return path;
|
|
11
|
+
const parent = dirname(dir);
|
|
12
|
+
if (parent === dir)
|
|
13
|
+
throw new Error('The bundled plugin-authoring skill is missing.');
|
|
14
|
+
dir = parent;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Variant skills win on a name collision, without modifying a pinned variant. */
|
|
18
|
+
export function defaultSkillPaths(variantDir) {
|
|
19
|
+
return variantDir && existsSync(join(variantDir, 'skills', 'plugin-authoring', 'SKILL.md')) ? [] : [defaultSkillPath()];
|
|
20
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type HarnessCall = 'recall.search' | 'recall.read' | 'trajectory.search' | 'trajectory.read' | 'wiki.search' | 'wiki.read' | 'wiki.list' | 'wiki.write' | 'skill.load';
|
|
1
|
+
export type HarnessCall = 'recall.search' | 'recall.read' | 'trajectory.search' | 'trajectory.read' | 'wiki.search' | 'wiki.read' | 'wiki.list' | 'wiki.write' | 'skill.load' | 'plugin.propose';
|
|
2
2
|
/** A JSON Schema object, kept loose so TypeBox, MCP, and the CLI can all use it. */
|
|
3
3
|
export type JsonSchema = {
|
|
4
4
|
type: string;
|
|
@@ -23,6 +23,8 @@ export declare const WIKI_WRITE = "wiki_write";
|
|
|
23
23
|
export declare const WIKI_WRITE_TOOLS: HarnessToolDefinition[];
|
|
24
24
|
/** Skills: load one of this agent's skills from its harness. */
|
|
25
25
|
export declare const SKILL_TOOLS: HarnessToolDefinition[];
|
|
26
|
+
/** Propose a package for a person to review; this never installs or enables it. */
|
|
27
|
+
export declare const PLUGIN_PROPOSAL_TOOLS: HarnessToolDefinition[];
|
|
26
28
|
/** Every harness tool. */
|
|
27
29
|
export declare const HARNESS_TOOLS: HarnessToolDefinition[];
|
|
28
30
|
/** The harness tools every session has; `wiki_write` is added only when the caller switches it on. */
|
|
@@ -110,8 +110,24 @@ export const SKILL_TOOLS = [
|
|
|
110
110
|
parameters: { type: 'object', properties: { name: text('The skill name, as your instructions write it') } }
|
|
111
111
|
}
|
|
112
112
|
];
|
|
113
|
+
/** Propose a package for a person to review; this never installs or enables it. */
|
|
114
|
+
export const PLUGIN_PROPOSAL_TOOLS = [{
|
|
115
|
+
name: 'plugin_propose',
|
|
116
|
+
call: 'plugin.propose',
|
|
117
|
+
label: 'Propose a plugin',
|
|
118
|
+
description: 'Propose a plugin for a person to review. This does not install, enable, or trust it. Use the plugin-authoring skill first. Provide text files as a map from relative paths to their complete contents.',
|
|
119
|
+
parameters: {
|
|
120
|
+
type: 'object',
|
|
121
|
+
properties: {
|
|
122
|
+
name: text('Lowercase plugin package name, using letters, numbers, hyphens or underscores'),
|
|
123
|
+
description: text('A short description of the plugin'),
|
|
124
|
+
files: { type: 'object', additionalProperties: { type: 'string' }, description: 'Relative file paths and complete UTF-8 text contents; omit package.json' }
|
|
125
|
+
},
|
|
126
|
+
required: ['name', 'description', 'files']
|
|
127
|
+
}
|
|
128
|
+
}];
|
|
113
129
|
/** Every harness tool. */
|
|
114
|
-
export const HARNESS_TOOLS = [...RECALL_TOOLS, ...TRAJECTORY_TOOLS, ...WIKI_READ_TOOLS, ...WIKI_WRITE_TOOLS, ...SKILL_TOOLS];
|
|
130
|
+
export const HARNESS_TOOLS = [...RECALL_TOOLS, ...TRAJECTORY_TOOLS, ...WIKI_READ_TOOLS, ...WIKI_WRITE_TOOLS, ...SKILL_TOOLS, ...PLUGIN_PROPOSAL_TOOLS];
|
|
115
131
|
/** The harness tools every session has; `wiki_write` is added only when the caller switches it on. */
|
|
116
132
|
export const DEFAULT_TOOL_NAMES = HARNESS_TOOLS.filter((tool) => tool.name !== WIKI_WRITE).map((tool) => tool.name);
|
|
117
133
|
export const describeTool = (tool, productName) => tool.description.replaceAll('{product}', productName);
|
|
@@ -7,6 +7,8 @@ import { HarnessError, invalid } from "../core/errors.js";
|
|
|
7
7
|
import * as recall from "../history/recall.js";
|
|
8
8
|
import * as trajectory from "../history/trajectory.js";
|
|
9
9
|
import * as notes from "../wiki/notes.js";
|
|
10
|
+
import * as proposals from "../plugins/proposals.js";
|
|
11
|
+
import { defaultSkillPaths } from "./defaultSkills.js";
|
|
10
12
|
function str(params, key) {
|
|
11
13
|
const value = params[key];
|
|
12
14
|
return typeof value === 'string' ? value : '';
|
|
@@ -45,6 +47,10 @@ export function dispatch(env, context, call, params, options = {}) {
|
|
|
45
47
|
env.bus?.emit({ type: 'wiki.changed' });
|
|
46
48
|
return `Saved ${note.path}.`;
|
|
47
49
|
}
|
|
50
|
+
case 'plugin.propose': {
|
|
51
|
+
const proposal = proposals.create(env.root, params.name, params.description, params.files);
|
|
52
|
+
return `Proposed ${proposal.name} (${proposal.id}). A person can review it in the plugin manager; it is not installed or enabled.`;
|
|
53
|
+
}
|
|
48
54
|
case 'skill.load':
|
|
49
55
|
return loadSkill(options.variantDir ?? null, str(params, 'name'));
|
|
50
56
|
default:
|
|
@@ -54,6 +60,10 @@ export function dispatch(env, context, call, params, options = {}) {
|
|
|
54
60
|
/** A skill's text from the run's variant, or the list of its skills. */
|
|
55
61
|
export function loadSkill(variantDir, name) {
|
|
56
62
|
const skills = variantDir && existsSync(join(variantDir, 'skills')) ? readdirSync(join(variantDir, 'skills'), { withFileTypes: true }).filter((entry) => entry.isDirectory() && existsSync(join(variantDir, 'skills', entry.name, 'SKILL.md'))).map((entry) => entry.name).sort() : [];
|
|
63
|
+
const defaults = defaultSkillPaths(variantDir);
|
|
64
|
+
if (defaults.length)
|
|
65
|
+
skills.push('plugin-authoring');
|
|
66
|
+
skills.sort();
|
|
57
67
|
const wanted = name.trim().replace(/^skills\//, '').replace(/`/g, '');
|
|
58
68
|
if (!wanted)
|
|
59
69
|
return skills.length ? `Your skills: ${skills.join(', ')}. Load one with skill_load and its name.` : 'You have no skills.';
|
|
@@ -61,6 +71,8 @@ export function loadSkill(variantDir, name) {
|
|
|
61
71
|
throw invalid('A skill name is lowercase letters, digits, _ or -.');
|
|
62
72
|
if (!skills.includes(wanted))
|
|
63
73
|
return `There is no skill named ${wanted}.${skills.length ? ` Your skills: ${skills.join(', ')}.` : ''}`;
|
|
74
|
+
if (wanted === 'plugin-authoring' && defaults.length)
|
|
75
|
+
return readFileSync(defaults[0], 'utf8');
|
|
64
76
|
const dir = join(variantDir, 'skills', wanted);
|
|
65
77
|
const helpers = existsSync(join(dir, 'scripts')) ? readdirSync(join(dir, 'scripts')) : [];
|
|
66
78
|
return `${readFileSync(join(dir, 'SKILL.md'), 'utf8')}${helpers.length ? `\n\n(This skill's helpers are in ${join(dir, 'scripts')}: ${helpers.join(', ')}.)` : ''}`;
|
|
@@ -6,7 +6,7 @@ export function harnessTools(env, context, options = {}) {
|
|
|
6
6
|
label: tool.label,
|
|
7
7
|
description: describeTool(tool, env.productName),
|
|
8
8
|
parameters: (Type) => Type.Unsafe(tool.parameters),
|
|
9
|
-
readOnly: tool.name !== 'wiki_write',
|
|
9
|
+
readOnly: tool.name !== 'wiki_write' && tool.name !== 'plugin_propose',
|
|
10
10
|
execute: async (input) => {
|
|
11
11
|
try {
|
|
12
12
|
return dispatch(env, context, tool.call, input, { variantDir: options.variantDir }) || 'Nothing found.';
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import type { ToolSpec } from "./types.js";
|
|
2
2
|
export declare const SESSION_NAME = "session_name";
|
|
3
|
+
/** The trigger the prompt and the tool description share, so naming guidance cannot drift. */
|
|
4
|
+
export declare const SESSION_GUIDANCE: string;
|
|
3
5
|
export interface NamedSession {
|
|
4
6
|
readonly sessionName?: string;
|
|
5
7
|
setSessionName(name: string): void;
|
|
6
8
|
}
|
|
9
|
+
/** The one name validation every caller uses: a short, single-line, control-free sentence. */
|
|
10
|
+
export declare function normalizeSessionName(value: unknown): string;
|
|
11
|
+
/**
|
|
12
|
+
* A provisional name for a substantive first request. It is deliberately mechanical: the model
|
|
13
|
+
* can still revise it with session_name. Greetings and short conversational replies stay unnamed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function deriveSessionName(text: string): string | null;
|
|
7
16
|
export declare function sessionNameTool(current: () => NamedSession | undefined): ToolSpec;
|
|
@@ -2,24 +2,71 @@
|
|
|
2
2
|
// the current native session; it never receives a target session id from the model.
|
|
3
3
|
import { invalid } from "../core/errors.js";
|
|
4
4
|
export const SESSION_NAME = 'session_name';
|
|
5
|
+
/** The trigger the prompt and the tool description share, so naming guidance cannot drift. */
|
|
6
|
+
export const SESSION_GUIDANCE = 'If the request needs tools or changes state — not a greeting, thanks, or a short conversational reply — call session_name once before your first other tool call, naming the work from the request. ' +
|
|
7
|
+
'Use one short imperative sentence of at most six words. Revise the name only when the overall objective changes; keep the current name when it does not.';
|
|
8
|
+
/** The one name validation every caller uses: a short, single-line, control-free sentence. */
|
|
9
|
+
export function normalizeSessionName(value) {
|
|
10
|
+
if (typeof value !== 'string')
|
|
11
|
+
throw invalid('Send name as a short sentence of at most six words.');
|
|
12
|
+
if (/[\x00-\x08\x0e-\x1f\x7f-\x9f]/u.test(value)) {
|
|
13
|
+
throw invalid('Session names cannot contain terminal escapes or control characters.');
|
|
14
|
+
}
|
|
15
|
+
const name = value.replace(/\s+/gu, ' ').trim();
|
|
16
|
+
if (!name || name.split(' ').length > 6)
|
|
17
|
+
throw invalid('Use a non-empty session name of at most six words.');
|
|
18
|
+
return name;
|
|
19
|
+
}
|
|
20
|
+
const CONVERSATIONAL = /^(?:hi|hello|hey|yo|thanks|thank you|ok|okay|sure|great|nice|cool|sounds good|got it|good morning|good afternoon|good evening|how are you|what'?s up)[.!?]*$/iu;
|
|
21
|
+
const LEADING = /^(?:please|can you|could you|would you|i want you to|i'd like you to|help me|let'?s|go ahead and)[,\s]+/iu;
|
|
22
|
+
/**
|
|
23
|
+
* A provisional name for a substantive first request. It is deliberately mechanical: the model
|
|
24
|
+
* can still revise it with session_name. Greetings and short conversational replies stay unnamed.
|
|
25
|
+
*/
|
|
26
|
+
export function deriveSessionName(text) {
|
|
27
|
+
if (typeof text !== 'string')
|
|
28
|
+
return null;
|
|
29
|
+
const input = text.trim();
|
|
30
|
+
if (!input || input.startsWith('!') || input.startsWith('```') || /^https?:\/\//iu.test(input))
|
|
31
|
+
return null;
|
|
32
|
+
if (CONVERSATIONAL.test(input))
|
|
33
|
+
return null;
|
|
34
|
+
if (/^continue\b/iu.test(input))
|
|
35
|
+
return 'Continue previous work';
|
|
36
|
+
const sentence = input.split(/[.!?](?:\s|$)/u)[0] ?? input;
|
|
37
|
+
const cleaned = sentence
|
|
38
|
+
.replace(/```[\s\S]*?```/gu, ' ')
|
|
39
|
+
.replace(/[`*_~#>]/gu, ' ')
|
|
40
|
+
.replace(/[:;,]+/gu, ' ')
|
|
41
|
+
.replace(LEADING, '')
|
|
42
|
+
.replace(/\s+/gu, ' ')
|
|
43
|
+
.trim();
|
|
44
|
+
if (!/[a-z]/iu.test(cleaned))
|
|
45
|
+
return null;
|
|
46
|
+
const words = cleaned.split(' ').filter(Boolean).slice(0, 6);
|
|
47
|
+
if (!words.length)
|
|
48
|
+
return null;
|
|
49
|
+
const name = words.join(' ').replace(/^[\p{P}\p{S}]+/u, '').replace(/[\p{P}\p{S}]+$/u, '').trim();
|
|
50
|
+
if (!name)
|
|
51
|
+
return null;
|
|
52
|
+
const capitalized = name[0].toUpperCase() + name.slice(1);
|
|
53
|
+
try {
|
|
54
|
+
return normalizeSessionName(capitalized);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
5
60
|
export function sessionNameTool(current) {
|
|
6
61
|
return {
|
|
7
62
|
name: SESSION_NAME,
|
|
8
63
|
label: 'Name the session',
|
|
9
|
-
description:
|
|
10
|
-
'
|
|
11
|
-
'You may revise a manually assigned name. Keep a useful current name when the objective is unchanged. This only names your own session.',
|
|
64
|
+
description: `Set or revise this session’s name. ${SESSION_GUIDANCE} ` +
|
|
65
|
+
'For example, "Add web browser integrations". You may revise a manually assigned name. This only names your own session.',
|
|
12
66
|
parameters: Type => Type.Object({ name: Type.String({ description: 'One short sentence describing the objective, at most six words' }) }),
|
|
13
67
|
planModeSafe: () => true,
|
|
14
68
|
execute: async (input) => {
|
|
15
|
-
|
|
16
|
-
throw invalid('Send name as a short sentence of at most six words.');
|
|
17
|
-
if (/[\x00-\x08\x0e-\x1f\x7f-\x9f]/u.test(input.name)) {
|
|
18
|
-
throw invalid('Session names cannot contain terminal escapes or control characters.');
|
|
19
|
-
}
|
|
20
|
-
const name = input.name.replace(/\s+/gu, ' ').trim();
|
|
21
|
-
if (!name || name.split(' ').length > 6)
|
|
22
|
-
throw invalid('Use a non-empty session name of at most six words.');
|
|
69
|
+
const name = normalizeSessionName(input.name);
|
|
23
70
|
const session = current();
|
|
24
71
|
if (!session)
|
|
25
72
|
throw invalid('The native session is unavailable; try naming it again once it is ready.');
|
|
@@ -27,20 +27,22 @@ export function subagentTools(service, parentRunId) {
|
|
|
27
27
|
return [{
|
|
28
28
|
name: SPAWN_SUBAGENT,
|
|
29
29
|
label: 'Start a subagent',
|
|
30
|
-
description: 'Start a fresh child run for independent work. Delegate substantial, bounded tasks that benefit from a separate context, not simple lookups, tiny edits or tightly coupled steps. Prefer a named specialist profile; omit agent only when no profile fits. Plan mode permits only eligible read-only research profiles. Give a short title and a self-contained brief: the child has no conversation history and no more capabilities than you. Returns its run ID immediately by default so you can continue other useful work. Results arrive automatically before you finish. Use wait_subagents when you need results before proceeding, or wait: true for an inline result. Avoid overlapping write assignments. Stopping you stops your children.',
|
|
30
|
+
description: 'Start a fresh child run for independent work. Delegate substantial, bounded tasks that benefit from a separate context, not simple lookups, tiny edits or tightly coupled steps. Prefer a named specialist profile; omit agent only when no profile fits. Plan mode permits only eligible read-only research profiles. Give a short title and a self-contained brief: the child has no conversation history and no more capabilities than you. The optional tools field only narrows the profile and parent intersection; in plan mode, omit it unless you need a stricter subset. Returns its run ID immediately by default so you can continue other useful work. Results arrive automatically before you finish. Use wait_subagents when you need results before proceeding, or wait: true for an inline result. Avoid overlapping write assignments. Stopping you stops your children. To resume a child that stopped before finishing (interrupted or failed), pass continue with its run ID and no title, agent or tools: it reopens that child\'s own conversation with the same profile and tools, and brief becomes an optional extra instruction.',
|
|
31
31
|
planModeSafe: input => service.planSpawnRefusal(parentRunId, input) === null,
|
|
32
32
|
parameters: (Type) => Type.Object({
|
|
33
|
-
title: Type.String({ description: `
|
|
34
|
-
brief: Type.String({ description: '
|
|
33
|
+
title: Type.Optional(Type.String({ description: `Required for a new child: a short imperative title for the roster, at most ${MAX_SUBAGENT_TITLE_WORDS} words, such as "Map the auth flow"` })),
|
|
34
|
+
brief: Type.Optional(Type.String({ description: 'Required for a new child: a self-contained task: goal, relevant context or paths, non-goals, expected result, and acceptance or verification. The child cannot see this conversation. With continue, an optional extra instruction.' })),
|
|
35
|
+
continue: Type.Optional(Type.String({ description: 'The run ID of one of your subagents that stopped before finishing, to continue it from where it stopped instead of starting a fresh child' })),
|
|
35
36
|
agent: Type.Optional(Type.String({ description: 'The name of a specialist profile from your Subagents list; omit only when no listed profile fits (omitting starts an unprofiled copy)' })),
|
|
36
|
-
tools: Type.Optional(Type.Array(Type.String())),
|
|
37
|
+
tools: Type.Optional(Type.Array(Type.String({ description: 'Only request a stricter subset of the profile and parent tools. In plan mode, omit this field to inherit the safe available research intersection.' }))),
|
|
37
38
|
wait: Type.Optional(Type.Boolean({ description: 'Wait for the child and return its result; default false' }))
|
|
38
39
|
}),
|
|
39
40
|
execute: execute(async (input) => {
|
|
40
|
-
const
|
|
41
|
-
|
|
41
|
+
const resume = input.continue === undefined ? undefined : input.continue;
|
|
42
|
+
const title = resume === undefined ? subagentTitle(input.title) : undefined;
|
|
43
|
+
if (resume === undefined && !title)
|
|
42
44
|
throw invalid(`Give the subagent a title of at most ${MAX_SUBAGENT_TITLE_WORDS} words, with no control characters.`);
|
|
43
|
-
const child = await service.spawn({ parentRunId, brief: input.brief, title, agent: input.agent, tools: input.tools, wait: input.wait });
|
|
45
|
+
const child = await service.spawn({ parentRunId, brief: input.brief, title, agent: input.agent, tools: input.tools, wait: input.wait, ...(resume === undefined ? {} : { continue: resume }) });
|
|
44
46
|
if (input.wait === true)
|
|
45
47
|
return reported(parentRunId, service.results({ parentRunId, runIds: [child.runId] }), [child.runId]);
|
|
46
48
|
return { text: JSON.stringify(child), details: { [SUBAGENT_RECEIPT]: { version: 1, ownerRunId: parentRunId, spawned: [child.runId] } } };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type TodoItem, type RunEvent } from "../sessions/contracts.js";
|
|
2
|
+
import type { ActiveRun } from "../sessions/registry.js";
|
|
3
|
+
import type { ToolSpec } from "./types.js";
|
|
4
|
+
export declare const TODO_WRITE = "todo_write";
|
|
5
|
+
/** The trigger the prompt and the tool description share, so progress guidance cannot drift. */
|
|
6
|
+
export declare const TODO_GUIDANCE: string;
|
|
7
|
+
/** The todo list as the model reads it back. */
|
|
8
|
+
export declare function describeTodo(items: readonly TodoItem[]): string;
|
|
9
|
+
/** The latest todo list a run wrote, or an empty list. Reads the pre-rename event too. */
|
|
10
|
+
export declare function todoOf(events: readonly RunEvent[]): TodoItem[];
|
|
11
|
+
export declare function todoTool(run: Pick<ActiveRun, 'emit'>): ToolSpec;
|
|
12
|
+
/** @deprecated Use `TODO_WRITE`; kept for one release so embedders can migrate. */
|
|
13
|
+
export declare const CHECKLIST_WRITE = "todo_write";
|
|
14
|
+
/** @deprecated Use `todoOf`; kept for one release so embedders can migrate. */
|
|
15
|
+
export declare const checklistOf: typeof todoOf;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// The run's own todo list: the model writes the whole list each time, and the run records it as
|
|
2
|
+
// a `todo.updated` event. It only changes run state, so it stays callable in plan mode.
|
|
3
|
+
import { todoItemSchema, MAX_TODO_ITEMS } from "../sessions/contracts.js";
|
|
4
|
+
export const TODO_WRITE = 'todo_write';
|
|
5
|
+
/** The trigger the prompt and the tool description share, so progress guidance cannot drift. */
|
|
6
|
+
export const TODO_GUIDANCE = 'If the work has several distinct steps or will take more than one tool call, call todo_write before your first other tool call with the steps you know so far, and keep the list current as steps start and finish. ' +
|
|
7
|
+
'Before your final reply, write one final update: mark every fully completed step completed and leave unfinished or blocked steps accurate. Never mark unfinished work completed. ' +
|
|
8
|
+
'The person follows your work from it, and a subagent’s todo list shows as its progress in the person’s list of subagents.';
|
|
9
|
+
/** The event a run writes; `checklist.updated` is the pre-rename name, still read for old runs. */
|
|
10
|
+
const TODO_EVENTS = new Set(['todo.updated', 'checklist.updated']);
|
|
11
|
+
const MARK = { pending: '[ ]', in_progress: '[~]', completed: '[x]' };
|
|
12
|
+
/** The todo list as the model reads it back. */
|
|
13
|
+
export function describeTodo(items) {
|
|
14
|
+
if (!items.length)
|
|
15
|
+
return 'The todo list is empty.';
|
|
16
|
+
return items.map((item) => `${MARK[item.status]} ${item.text}`).join('\n');
|
|
17
|
+
}
|
|
18
|
+
/** The latest todo list a run wrote, or an empty list. Reads the pre-rename event too. */
|
|
19
|
+
export function todoOf(events) {
|
|
20
|
+
const last = events.filter((event) => TODO_EVENTS.has(event.type)).at(-1);
|
|
21
|
+
const items = last?.payload?.items;
|
|
22
|
+
return Array.isArray(items) ? items.flatMap((item) => {
|
|
23
|
+
const parsed = todoItemSchema.safeParse(item);
|
|
24
|
+
return parsed.success ? [parsed.data] : [];
|
|
25
|
+
}) : [];
|
|
26
|
+
}
|
|
27
|
+
export function todoTool(run) {
|
|
28
|
+
return {
|
|
29
|
+
name: TODO_WRITE,
|
|
30
|
+
label: 'Write the todo list',
|
|
31
|
+
description: `Keep a todo list of the steps of the current work. The user watches this list to follow your progress. ${TODO_GUIDANCE} ` +
|
|
32
|
+
'Send the whole list every time; it replaces the previous one. Do not use it for a single, simple step or for conversation. ' +
|
|
33
|
+
'Statuses: pending, in_progress, completed. Keep exactly one step in_progress while you work, mark a step completed as soon as it is done (not in batches), and add steps you discover. ' +
|
|
34
|
+
'Only mark a step completed when it is fully done; if something blocks it, keep it in_progress and add a step for the blocker.',
|
|
35
|
+
parameters: (Type) => Type.Object({
|
|
36
|
+
items: Type.Array(Type.Object({
|
|
37
|
+
text: Type.String({ description: 'One step, as a short imperative line' }),
|
|
38
|
+
status: Type.Union([Type.Literal('pending'), Type.Literal('in_progress'), Type.Literal('completed')])
|
|
39
|
+
}), { maxItems: MAX_TODO_ITEMS })
|
|
40
|
+
}),
|
|
41
|
+
readOnly: true,
|
|
42
|
+
planModeSafe: () => true,
|
|
43
|
+
execute: async (input) => {
|
|
44
|
+
if (!Array.isArray(input.items))
|
|
45
|
+
return 'Send items: the whole todo list as an array.';
|
|
46
|
+
if (input.items.length > MAX_TODO_ITEMS)
|
|
47
|
+
return `Keep the todo list to ${MAX_TODO_ITEMS} steps or fewer.`;
|
|
48
|
+
const items = [];
|
|
49
|
+
for (const raw of input.items) {
|
|
50
|
+
const entry = raw;
|
|
51
|
+
const parsed = todoItemSchema.safeParse({ text: typeof entry?.text === 'string' ? entry.text.trim() : entry?.text, status: entry?.status });
|
|
52
|
+
if (!parsed.success)
|
|
53
|
+
return 'Every step needs text of 1 to 500 characters and a status of pending, in_progress or completed.';
|
|
54
|
+
items.push(parsed.data);
|
|
55
|
+
}
|
|
56
|
+
run.emit('todo.updated', { items });
|
|
57
|
+
return describeTodo(items);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** @deprecated Use `TODO_WRITE`; kept for one release so embedders can migrate. */
|
|
62
|
+
export const CHECKLIST_WRITE = TODO_WRITE;
|
|
63
|
+
/** @deprecated Use `todoOf`; kept for one release so embedders can migrate. */
|
|
64
|
+
export const checklistOf = todoOf;
|
|
@@ -27,8 +27,6 @@ export interface WorldOptions {
|
|
|
27
27
|
request?: WebRequest;
|
|
28
28
|
/** Replaces the browser's launcher and address check in tests. */
|
|
29
29
|
browser?: Omit<BrowserPartOptions, 'stateDir'> | false;
|
|
30
|
-
/** Told when the agent marks a page note fixed, so front ends refresh (PROP-0018). */
|
|
31
|
-
notesChanged?: (sessionId: string | null, ids: string[]) => void;
|
|
32
30
|
}
|
|
33
31
|
/** A family of world tools, switched as one plugin. */
|
|
34
32
|
export interface WorldPart {
|