@crouter/api 0.3.387 → 0.3.389
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/__tests__/integration/client.test.js +97 -0
- package/dist/api/client.d.ts +7 -0
- package/dist/api/client.js +40 -21
- package/dist/api/dto/config.d.ts +11 -1
- package/dist/core/asset-root.d.ts +7 -0
- package/dist/core/asset-root.js +18 -0
- package/dist/core/canvas/boot-id.d.ts +6 -0
- package/dist/core/canvas/boot-id.js +26 -0
- package/dist/core/canvas/paths.d.ts +72 -0
- package/dist/core/canvas/paths.js +163 -0
- package/dist/core/canvas/pid.d.ts +391 -0
- package/dist/core/canvas/pid.js +948 -0
- package/dist/core/command-plugins/bundle.d.ts +149 -0
- package/dist/core/command-plugins/bundle.js +588 -0
- package/dist/core/command-plugins/endpoint.d.ts +24 -0
- package/dist/core/command-plugins/endpoint.js +51 -0
- package/dist/core/config.d.ts +233 -0
- package/dist/core/config.js +1120 -0
- package/dist/core/env-name.d.ts +6 -0
- package/dist/core/env-name.js +9 -0
- package/dist/core/errors.d.ts +38 -0
- package/dist/core/errors.js +90 -0
- package/dist/core/events/emit.d.ts +6 -0
- package/dist/core/events/emit.js +42 -0
- package/dist/core/events/envelope.d.ts +2 -0
- package/dist/core/events/envelope.js +84 -0
- package/dist/core/events/errors.d.ts +4 -0
- package/dist/core/events/errors.js +69 -0
- package/dist/core/events/operation-id.d.ts +4 -0
- package/dist/core/events/operation-id.js +24 -0
- package/dist/core/events/serialize.d.ts +4 -0
- package/dist/core/events/serialize.js +199 -0
- package/dist/core/events/source.d.ts +16 -0
- package/dist/core/events/source.js +31 -0
- package/dist/core/events/types.d.ts +68 -0
- package/dist/core/events/types.js +11 -0
- package/dist/core/exclusive-lock.d.ts +34 -0
- package/dist/core/exclusive-lock.js +197 -0
- package/dist/core/fs-utils.d.ts +44 -0
- package/dist/core/fs-utils.js +208 -0
- package/dist/core/help.d.ts +309 -0
- package/dist/core/help.js +406 -0
- package/dist/core/human/page-catalog.d.ts +57 -0
- package/dist/core/human/page-catalog.js +172 -0
- package/dist/core/installed-plugins.d.ts +2 -0
- package/dist/core/installed-plugins.js +79 -0
- package/dist/core/io.d.ts +122 -0
- package/dist/core/io.js +373 -0
- package/dist/core/keybindings/attach-control.d.ts +49 -0
- package/dist/core/keybindings/attach-control.js +42 -0
- package/dist/core/keybindings/catalog.d.ts +18 -0
- package/dist/core/keybindings/catalog.js +257 -0
- package/dist/core/keybindings/types.d.ts +42 -0
- package/dist/core/keybindings/types.js +1 -0
- package/dist/core/layout.d.ts +26 -0
- package/dist/core/layout.js +94 -0
- package/dist/core/locked-file.d.ts +27 -0
- package/dist/core/locked-file.js +118 -0
- package/dist/core/log.d.ts +9 -0
- package/dist/core/log.js +89 -0
- package/dist/core/manifest.d.ts +5 -0
- package/dist/core/manifest.js +15 -0
- package/dist/core/plugin-env.d.ts +8 -0
- package/dist/core/plugin-env.js +31 -0
- package/dist/core/plugin-extensions.d.ts +29 -0
- package/dist/core/plugin-extensions.js +191 -0
- package/dist/core/plugin-swap-lock.d.ts +9 -0
- package/dist/core/plugin-swap-lock.js +31 -0
- package/dist/core/preview-result-path.d.ts +4 -0
- package/dist/core/preview-result-path.js +26 -0
- package/dist/core/profiles/env-store.d.ts +22 -0
- package/dist/core/profiles/env-store.js +163 -0
- package/dist/core/profiles/fuzzy-match.d.ts +19 -0
- package/dist/core/profiles/fuzzy-match.js +92 -0
- package/dist/core/profiles/manifest.d.ts +120 -0
- package/dist/core/profiles/manifest.js +529 -0
- package/dist/core/rate-limit-scope.d.ts +25 -0
- package/dist/core/rate-limit-scope.js +64 -0
- package/dist/core/render.d.ts +12 -0
- package/dist/core/render.js +138 -0
- package/dist/core/resolver.d.ts +14 -0
- package/dist/core/resolver.js +111 -0
- package/dist/core/runtime/branded-host.d.ts +25 -0
- package/dist/core/runtime/branded-host.js +264 -0
- package/dist/core/runtime/broker/daemon-ops.d.ts +65 -0
- package/dist/core/runtime/broker/daemon-ops.js +177 -0
- package/dist/core/runtime/broker/signal-stream.d.ts +30 -0
- package/dist/core/runtime/broker/signal-stream.js +149 -0
- package/dist/core/scope.d.ts +32 -0
- package/dist/core/scope.js +184 -0
- package/dist/core/scoped-state/db.d.ts +17 -0
- package/dist/core/scoped-state/db.js +247 -0
- package/dist/core/scoped-state/migrate.d.ts +8 -0
- package/dist/core/scoped-state/migrate.js +187 -0
- package/dist/core/scoped-state/paths.d.ts +9 -0
- package/dist/core/scoped-state/paths.js +27 -0
- package/dist/core/scoped-state/profiles.d.ts +27 -0
- package/dist/core/scoped-state/profiles.js +93 -0
- package/dist/core/scoped-state/providers.d.ts +24 -0
- package/dist/core/scoped-state/providers.js +19 -0
- package/dist/core/scoped-state/schema.d.ts +6 -0
- package/dist/core/scoped-state/schema.js +43 -0
- package/dist/core/scoped-state/settings.d.ts +28 -0
- package/dist/core/scoped-state/settings.js +83 -0
- package/dist/core/spaces/open-beneath.d.ts +71 -0
- package/dist/core/spaces/open-beneath.js +581 -0
- package/dist/core/sqlite-statements.d.ts +4 -0
- package/dist/core/sqlite-statements.js +17 -0
- package/dist/core/subscription-state.d.ts +121 -0
- package/dist/core/subscription-state.js +287 -0
- package/dist/core/user-settings.d.ts +377 -0
- package/dist/core/user-settings.js +458 -0
- package/dist/daemon/broker-signals/bus.d.ts +30 -0
- package/dist/daemon/broker-signals/bus.js +87 -0
- package/dist/daemon/manage.d.ts +176 -0
- package/dist/daemon/manage.js +664 -0
- package/dist/daemon/pidfile.d.ts +8 -0
- package/dist/daemon/pidfile.js +37 -0
- package/dist/daemon/startup-policy.d.ts +1 -0
- package/dist/daemon/startup-policy.js +1 -0
- package/dist/native/linux.d.ts +29 -0
- package/dist/native/linux.js +20 -0
- package/dist/shared/env.d.ts +116 -0
- package/dist/shared/env.js +271 -0
- package/dist/shared/inbox-entry-body.d.ts +22 -0
- package/dist/shared/inbox-entry-body.js +116 -0
- package/dist/shared/working-activity.d.ts +9 -0
- package/dist/shared/working-activity.js +27 -0
- package/dist/types.d.ts +562 -0
- package/dist/types.js +186 -0
- package/package.json +1 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Default stdout rendering: the result a leaf returns is written FOR the model
|
|
2
|
+
// to act on, not as data to parse. Output is a continuation of the agent's
|
|
3
|
+
// prompt — PLAIN MARKDOWN, no XML wrapper. Scalars list as `- name: value`
|
|
4
|
+
// bullets in output-schema order, prose fields render as paragraphs, and arrays
|
|
5
|
+
// become a count lead-in plus a markdown table (objects) or bullets (scalars).
|
|
6
|
+
// The raw JSON object is still available behind the `--json` global for tooling.
|
|
7
|
+
//
|
|
8
|
+
// A leaf may hand `defineLeaf` a bespoke `render(result)` for instruction-shaped
|
|
9
|
+
// output that leads with the outcome (see `node new`, `push`).
|
|
10
|
+
// Everything else falls through to the schema-driven generic renderer here, so
|
|
11
|
+
// every command obeys the markdown paradigm without a hand-written renderer.
|
|
12
|
+
// `renderError` is the one sanctioned exception: a failure is a different domain
|
|
13
|
+
// from a result, so it keeps its `<error>` block.
|
|
14
|
+
// Field names whose value is prose the agent reads, not a scalar it keys on —
|
|
15
|
+
// these always render as their own paragraph, never as a `- name: value` bullet.
|
|
16
|
+
const PROSE_FIELDS = new Set([
|
|
17
|
+
'follow_up', 'digest', 'message', 'next', 'result', 'content', 'body', 'guide',
|
|
18
|
+
'summary', 'note', 'reason', 'detail', 'details', 'hint', 'instruction',
|
|
19
|
+
'instructions', 'prompt', 'roadmap', 'report', 'answer', 'response', 'text',
|
|
20
|
+
]);
|
|
21
|
+
/** Minimal attribute-value sanitization: values here are controlled (ids,
|
|
22
|
+
* statuses, counts), so collapse quotes rather than entity-escape. */
|
|
23
|
+
function attrEsc(s) {
|
|
24
|
+
return String(s).replace(/"/g, "'").replace(/\n/g, ' ');
|
|
25
|
+
}
|
|
26
|
+
function scalarStr(v) {
|
|
27
|
+
if (v === null || v === undefined)
|
|
28
|
+
return '';
|
|
29
|
+
if (Array.isArray(v))
|
|
30
|
+
return v.map(scalarStr).join(', ');
|
|
31
|
+
if (typeof v === 'object')
|
|
32
|
+
return JSON.stringify(v);
|
|
33
|
+
return String(v);
|
|
34
|
+
}
|
|
35
|
+
function fencedJson(value) {
|
|
36
|
+
const json = JSON.stringify(value, null, 2);
|
|
37
|
+
let fence = '```';
|
|
38
|
+
while (json.includes(fence))
|
|
39
|
+
fence += '`';
|
|
40
|
+
return `${fence}json\n${json}\n${fence}`;
|
|
41
|
+
}
|
|
42
|
+
/** A string field is prose when it is named as such, spans lines, or is long
|
|
43
|
+
* enough that it reads as a sentence rather than an identifier. */
|
|
44
|
+
function isProse(name, v) {
|
|
45
|
+
if (typeof v !== 'string')
|
|
46
|
+
return false;
|
|
47
|
+
if (PROSE_FIELDS.has(name))
|
|
48
|
+
return true;
|
|
49
|
+
return v.includes('\n') || v.length > 80;
|
|
50
|
+
}
|
|
51
|
+
/** Keep a value safe inside a markdown table cell: pipes would split columns,
|
|
52
|
+
* newlines would break the row. */
|
|
53
|
+
function cellEsc(v) {
|
|
54
|
+
return scalarStr(v).replace(/\|/g, '\\|').replace(/\n/g, ' ');
|
|
55
|
+
}
|
|
56
|
+
/** An array renders as a count lead-in followed by the collection: a markdown
|
|
57
|
+
* table when every element is an object, plain bullets otherwise. */
|
|
58
|
+
function renderArray(name, arr) {
|
|
59
|
+
if (arr.length === 0)
|
|
60
|
+
return `0 ${name}.`;
|
|
61
|
+
const lead = `${arr.length} ${name}:`;
|
|
62
|
+
const allObjects = arr.every((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
63
|
+
if (allObjects) {
|
|
64
|
+
const rows = arr;
|
|
65
|
+
const cols = [];
|
|
66
|
+
for (const r of rows)
|
|
67
|
+
for (const k of Object.keys(r))
|
|
68
|
+
if (!cols.includes(k))
|
|
69
|
+
cols.push(k);
|
|
70
|
+
const head = `| ${cols.join(' | ')} |`;
|
|
71
|
+
const sep = `| ${cols.map(() => '---').join(' | ')} |`;
|
|
72
|
+
const body = rows.map((r) => `| ${cols.map((c) => cellEsc(r[c])).join(' | ')} |`).join('\n');
|
|
73
|
+
return `${lead}\n\n${head}\n${sep}\n${body}`;
|
|
74
|
+
}
|
|
75
|
+
const items = arr.map((x) => `- ${scalarStr(x)}`).join('\n');
|
|
76
|
+
return `${lead}\n${items}`;
|
|
77
|
+
}
|
|
78
|
+
/** Schema-driven fallback: turn a result object into agent-ready plain markdown.
|
|
79
|
+
* Field order follows the leaf's declared output schema; any extra keys append
|
|
80
|
+
* after. Scalars (and nested objects) list as `- name: value` bullets, prose
|
|
81
|
+
* fields render as paragraphs, and arrays become a count lead-in plus a table
|
|
82
|
+
* or bullets. No root tag — the result is read as a continuation of the prompt. */
|
|
83
|
+
export function renderResult(result, help) {
|
|
84
|
+
const order = help.output.map((f) => f.name);
|
|
85
|
+
for (const k of Object.keys(result))
|
|
86
|
+
if (!order.includes(k))
|
|
87
|
+
order.push(k);
|
|
88
|
+
const bullets = [];
|
|
89
|
+
const proseFields = [];
|
|
90
|
+
const arrays = [];
|
|
91
|
+
for (const name of order) {
|
|
92
|
+
const v = result[name];
|
|
93
|
+
if (v === undefined || v === null)
|
|
94
|
+
continue;
|
|
95
|
+
if (Array.isArray(v)) {
|
|
96
|
+
arrays.push(renderArray(name, v));
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (isProse(name, v)) {
|
|
100
|
+
proseFields.push({ name, text: v });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
// Scalars and nested objects alike list as a bullet (objects via compact JSON).
|
|
104
|
+
bullets.push(`- ${name}: ${scalarStr(v)}`);
|
|
105
|
+
}
|
|
106
|
+
const parts = [];
|
|
107
|
+
if (bullets.length > 0)
|
|
108
|
+
parts.push(bullets.join('\n'));
|
|
109
|
+
// Label prose only when more than one block would otherwise blur together.
|
|
110
|
+
const labelProse = proseFields.length > 1;
|
|
111
|
+
for (const { name, text } of proseFields) {
|
|
112
|
+
parts.push(labelProse ? `**${name}:** ${text}` : text);
|
|
113
|
+
}
|
|
114
|
+
for (const block of arrays)
|
|
115
|
+
parts.push(block);
|
|
116
|
+
return parts.join('\n\n');
|
|
117
|
+
}
|
|
118
|
+
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
119
|
+
* was received, and the recovery road sign — the same recovery info the JSON
|
|
120
|
+
* payload carries, shaped for the model to act on. */
|
|
121
|
+
export function renderError(p) {
|
|
122
|
+
const lines = [p.message];
|
|
123
|
+
if (p.received !== undefined && p.received !== null && p.received !== '') {
|
|
124
|
+
lines.push(`received: ${scalarStr(p.received)}`);
|
|
125
|
+
}
|
|
126
|
+
if (p.field !== undefined)
|
|
127
|
+
lines.push(`field: ${p.field}`);
|
|
128
|
+
// Its own labeled line: a transport status is a fact about the call, not about
|
|
129
|
+
// the value the caller supplied.
|
|
130
|
+
if (p.http_status !== undefined)
|
|
131
|
+
lines.push(`http status: ${p.http_status}`);
|
|
132
|
+
if ('primaryCompleted' in p && p.primaryCompleted === true) {
|
|
133
|
+
lines.push('primaryCompleted: true');
|
|
134
|
+
lines.push(`Primary result (the command already completed):\n${fencedJson(p.result)}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push(`Next: ${p.next}`);
|
|
137
|
+
return `<error code="${attrEsc(p.error)}">\n${lines.join('\n')}\n</error>`;
|
|
138
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { InstalledMarketplace, InstalledPlugin, Scope } from '../types.js';
|
|
2
|
+
export { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
3
|
+
export declare function listInstalledPlugins(scope: Scope): InstalledPlugin[];
|
|
4
|
+
export declare function listAllPlugins(): InstalledPlugin[];
|
|
5
|
+
export declare function findPluginByName(name: string, scope?: Scope): InstalledPlugin | null;
|
|
6
|
+
export interface ParsedSkillQualifier {
|
|
7
|
+
scope?: Scope;
|
|
8
|
+
segments: string[];
|
|
9
|
+
}
|
|
10
|
+
export declare function parseSkillQualifier(raw: string): ParsedSkillQualifier;
|
|
11
|
+
export declare function listInstalledMarketplaces(scope: Exclude<Scope, 'builtin'>): InstalledMarketplace[];
|
|
12
|
+
export declare function listAllMarketplaces(): InstalledMarketplace[];
|
|
13
|
+
export declare function findMarketplaceByName(name: string, scope?: Exclude<Scope, 'builtin'>): InstalledMarketplace | null;
|
|
14
|
+
export declare function scopeRootsLabel(): string;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { readConfig } from './config.js';
|
|
3
|
+
import { listDirs, pathExists } from './fs-utils.js';
|
|
4
|
+
import { readMarketplaceManifest } from './manifest.js';
|
|
5
|
+
import { InputError } from './io.js';
|
|
6
|
+
import { marketplacesDir, projectScopeRoot, scopeRoot, userScopeRoot, } from './scope.js';
|
|
7
|
+
// Moved to installed-plugins.ts (a leaf module below config.ts) so
|
|
8
|
+
// `readMergedLaunchConfig` can layer plugin-declared kinds without an import
|
|
9
|
+
// cycle; re-exported here for this module's existing callers.
|
|
10
|
+
export { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
11
|
+
import { listInstalledPluginsInRoot } from './installed-plugins.js';
|
|
12
|
+
export function listInstalledPlugins(scope) {
|
|
13
|
+
// The builtin scope has no scopeRoot, so this returns [] — builtin content is
|
|
14
|
+
// the shipped documents, not plugins.
|
|
15
|
+
const root = scopeRoot(scope);
|
|
16
|
+
if (!root)
|
|
17
|
+
return [];
|
|
18
|
+
return listInstalledPluginsInRoot(scope, root);
|
|
19
|
+
}
|
|
20
|
+
export function listAllPlugins() {
|
|
21
|
+
const scopes = [];
|
|
22
|
+
if (projectScopeRoot())
|
|
23
|
+
scopes.push('project');
|
|
24
|
+
scopes.push('user');
|
|
25
|
+
scopes.push('builtin');
|
|
26
|
+
return scopes.flatMap(listInstalledPlugins);
|
|
27
|
+
}
|
|
28
|
+
export function findPluginByName(name, scope) {
|
|
29
|
+
if (scope) {
|
|
30
|
+
return listInstalledPlugins(scope).find((p) => p.name === name) ?? null;
|
|
31
|
+
}
|
|
32
|
+
for (const s of ['project', 'user', 'builtin'].filter((sc) => sc === 'project' ? projectScopeRoot() !== null : true)) {
|
|
33
|
+
const match = listInstalledPlugins(s).find((p) => p.name === name);
|
|
34
|
+
if (match)
|
|
35
|
+
return match;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const SCOPE_QUALIFIERS = new Set(['user', 'project']);
|
|
40
|
+
// Accepted identifier forms:
|
|
41
|
+
// <name> — bare name; scope-root first, then plugins
|
|
42
|
+
// <plugin>/<name> — explicit plugin (plugin may contain slashes)
|
|
43
|
+
// <scope>/<name> — scope-root in a specific scope
|
|
44
|
+
// <scope>/<plugin>/<name> — fully qualified
|
|
45
|
+
export function parseSkillQualifier(raw) {
|
|
46
|
+
if (raw.includes(':')) {
|
|
47
|
+
const suggested = raw.replace(/:/g, '/');
|
|
48
|
+
throw new InputError({
|
|
49
|
+
error: 'invalid_qualifier',
|
|
50
|
+
message: "mixed separators ':' and '/' no longer supported; use slashes throughout",
|
|
51
|
+
received: raw,
|
|
52
|
+
field: 'name',
|
|
53
|
+
next: `Use ${suggested}.`,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
const segments = raw.split('/');
|
|
57
|
+
if (SCOPE_QUALIFIERS.has(segments[0])) {
|
|
58
|
+
const scope = segments[0];
|
|
59
|
+
return { scope, segments: segments.slice(1) };
|
|
60
|
+
}
|
|
61
|
+
return { segments };
|
|
62
|
+
}
|
|
63
|
+
export function listInstalledMarketplaces(scope) {
|
|
64
|
+
const dir = marketplacesDir(scope);
|
|
65
|
+
if (!dir || !pathExists(dir))
|
|
66
|
+
return [];
|
|
67
|
+
const cfg = readConfig(scope);
|
|
68
|
+
const out = [];
|
|
69
|
+
for (const name of listDirs(dir)) {
|
|
70
|
+
const root = join(dir, name);
|
|
71
|
+
const manifest = readMarketplaceManifest(root);
|
|
72
|
+
if (!manifest)
|
|
73
|
+
continue;
|
|
74
|
+
const entry = cfg.marketplaces[name];
|
|
75
|
+
const url = entry && entry.url !== undefined ? entry.url : '';
|
|
76
|
+
const ref = entry && entry.ref !== undefined ? entry.ref : 'main';
|
|
77
|
+
out.push({
|
|
78
|
+
name,
|
|
79
|
+
scope,
|
|
80
|
+
root,
|
|
81
|
+
manifest,
|
|
82
|
+
url,
|
|
83
|
+
ref,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
export function listAllMarketplaces() {
|
|
89
|
+
const scopes = [];
|
|
90
|
+
if (projectScopeRoot())
|
|
91
|
+
scopes.push('project');
|
|
92
|
+
scopes.push('user');
|
|
93
|
+
return scopes.flatMap(listInstalledMarketplaces);
|
|
94
|
+
}
|
|
95
|
+
export function findMarketplaceByName(name, scope) {
|
|
96
|
+
if (scope) {
|
|
97
|
+
return listInstalledMarketplaces(scope).find((m) => m.name === name) ?? null;
|
|
98
|
+
}
|
|
99
|
+
for (const s of ['project', 'user']) {
|
|
100
|
+
if (s === 'project' && !projectScopeRoot())
|
|
101
|
+
continue;
|
|
102
|
+
const found = listInstalledMarketplaces(s).find((m) => m.name === name);
|
|
103
|
+
if (found)
|
|
104
|
+
return found;
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
export function scopeRootsLabel() {
|
|
109
|
+
const proj = projectScopeRoot();
|
|
110
|
+
return proj ? `project=${proj}, user=${userScopeRoot()}` : `user=${userScopeRoot()}`;
|
|
111
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Machine-global host dir in this process owner’s HOME. Only the daemon reconciles it; agents do not install the branded host. Deliberately NOT under `CRTR_HOME` (tests override
|
|
2
|
+
* that to an isolated temp): the branded binary is a per-machine artifact keyed
|
|
3
|
+
* to the installed node, shared across every canvas home. */
|
|
4
|
+
export declare function brandedHostDir(): string;
|
|
5
|
+
export declare function brandedHostBin(): string;
|
|
6
|
+
/** macOS keys FDA grants on the Mach-O cdhash. A real developer identity
|
|
7
|
+
* keeps that hash stable across branded-host rebuilds; ad-hoc signing is the
|
|
8
|
+
* portable default for machines without a local signing certificate. */
|
|
9
|
+
export declare function codesignIdentitiesForBrandedHost(opts?: {
|
|
10
|
+
configured?: string | null;
|
|
11
|
+
discovered?: string | null;
|
|
12
|
+
}): string[];
|
|
13
|
+
export declare function signBrandedHostArtifacts(binPath: string, libnodePath: string | null, sign?: (path: string) => void): void;
|
|
14
|
+
export declare function brandedHostWorks(binPath: string, timeoutMs?: number): boolean;
|
|
15
|
+
/** Ensure the branded host binary exists and matches the live node; return its
|
|
16
|
+
* absolute path. Idempotent and self-healing. Throws on a genuine build failure
|
|
17
|
+
* (disk full / permissions) — branding is load-bearing for FDA, so a failure is
|
|
18
|
+
* surfaced, never silently masked by falling back to `node`. */
|
|
19
|
+
export declare function ensureBrandedHost(): string;
|
|
20
|
+
/** The path the engine (broker / daemon) should be launched from. On macOS in a
|
|
21
|
+
* compiled (production) build this is the validated branded host; otherwise —
|
|
22
|
+
* non-darwin (TCC is macOS-only), source/tsx (dev + the test suite: never copy
|
|
23
|
+
* 70 MB into the real ~/.crouter), or the explicit `CRTR_BRANDED_HOST=0`
|
|
24
|
+
* opt-out — it is the live node. */
|
|
25
|
+
export declare function hostExecPath(): string;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// branded-host.ts — a crouter-branded copy of the `node` binary, so the
|
|
2
|
+
// long-lived engine processes (brokers + the crtrd daemon) show "crouter" in
|
|
3
|
+
// macOS' TCC / Full Disk Access list instead of the generic "node".
|
|
4
|
+
//
|
|
5
|
+
// WHY a real copied binary (not a symlink, not process.title): macOS keys a
|
|
6
|
+
// Full Disk Access entry on the Mach-O binary's *filename* — not argv[0], not
|
|
7
|
+
// process.title, and symlinks resolve to their realpath. So the ONLY way to show
|
|
8
|
+
// a branded name is to launch the engine from a real Mach-O file whose filename
|
|
9
|
+
// is the brand. We copy the node launcher to `~/.crouter/host/crouter`.
|
|
10
|
+
//
|
|
11
|
+
// WHY self-contained (libnode beside it): a Homebrew node is a small (~70 KB)
|
|
12
|
+
// launcher that dynamically loads `@rpath/libnode.<abi>.dylib` (~70 MB). node's
|
|
13
|
+
// LC_RPATH includes `@loader_path`, so a copy of that dylib placed *beside* the
|
|
14
|
+
// branded binary resolves with no DYLD_* env var (which SIP can strip) and no
|
|
15
|
+
// dependency on the Cellar path surviving — even after a `brew uninstall` of the
|
|
16
|
+
// old keg, the branded copy keeps running. A statically-linked node (nodejs.org)
|
|
17
|
+
// has no separate libnode; we just copy the binary. The remaining absolute
|
|
18
|
+
// `/opt/homebrew/opt/...` deps resolve by their baked-in absolute paths.
|
|
19
|
+
//
|
|
20
|
+
// SELF-HEAL: ensureBrandedHost() rebuilds the copy whenever the live node drifts
|
|
21
|
+
// (a `brew upgrade node` moves realpath(execPath) to a new Cellar version) or the
|
|
22
|
+
// copy is missing. The drift check is a cheap marker compare — no subprocess, no
|
|
23
|
+
// copy — on the happy path.
|
|
24
|
+
import { copyFileSync, mkdirSync, existsSync, readFileSync, writeFileSync, chmodSync, readdirSync, renameSync, realpathSync, unlinkSync, rmSync, linkSync, } from 'node:fs';
|
|
25
|
+
import { homedir } from 'node:os';
|
|
26
|
+
import { dirname, join } from 'node:path';
|
|
27
|
+
import { spawnSync } from 'node:child_process';
|
|
28
|
+
import { CRTR_DIR_NAME } from '../../types.js';
|
|
29
|
+
import { emitEvent } from '../events/emit.js';
|
|
30
|
+
import { withExclusiveDirectoryLock } from '../exclusive-lock.js';
|
|
31
|
+
/** The filename the FDA / TCC entry is shown under. */
|
|
32
|
+
const BRANDED_BIN_NAME = 'crouter';
|
|
33
|
+
/** Machine-global host dir in this process owner’s HOME. Only the daemon reconciles it; agents do not install the branded host. Deliberately NOT under `CRTR_HOME` (tests override
|
|
34
|
+
* that to an isolated temp): the branded binary is a per-machine artifact keyed
|
|
35
|
+
* to the installed node, shared across every canvas home. */
|
|
36
|
+
export function brandedHostDir() {
|
|
37
|
+
return join(homedir(), CRTR_DIR_NAME, 'host');
|
|
38
|
+
}
|
|
39
|
+
export function brandedHostBin() {
|
|
40
|
+
return join(brandedHostDir(), BRANDED_BIN_NAME);
|
|
41
|
+
}
|
|
42
|
+
function markerPath() {
|
|
43
|
+
return join(brandedHostDir(), '.source.json');
|
|
44
|
+
}
|
|
45
|
+
/** Locate the `libnode.<abi>.dylib` a Homebrew node loads via `@rpath`. node's
|
|
46
|
+
* rpaths are `@loader_path` and `@loader_path/../lib`, so check the binary's own
|
|
47
|
+
* dir first, then the sibling `lib/`. Returns the absolute path, or null for a
|
|
48
|
+
* statically-linked node that has no separate libnode. */
|
|
49
|
+
function findLibnode(nodeBin) {
|
|
50
|
+
const binDir = dirname(nodeBin);
|
|
51
|
+
for (const dir of [binDir, join(binDir, '..', 'lib')]) {
|
|
52
|
+
if (!existsSync(dir))
|
|
53
|
+
continue;
|
|
54
|
+
const hit = readdirSync(dir).find((f) => f.startsWith('libnode.') && f.endsWith('.dylib'));
|
|
55
|
+
if (hit !== undefined)
|
|
56
|
+
return join(dir, hit);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** Copy `src` → `dst` via a unique temp + atomic rename, so a failed copy
|
|
61
|
+
* never leaves a half-written artifact. Host-wide concurrency is serialized by
|
|
62
|
+
* the dedicated reconcile lock below. */
|
|
63
|
+
function atomicCopy(src, dst, mode) {
|
|
64
|
+
const tmp = `${dst}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
65
|
+
copyFileSync(src, tmp);
|
|
66
|
+
if (mode !== undefined)
|
|
67
|
+
chmodSync(tmp, mode);
|
|
68
|
+
renameSync(tmp, dst);
|
|
69
|
+
}
|
|
70
|
+
function readMarker(path = markerPath()) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const HOST_LOCK_TIMEOUT_MS = 30_000;
|
|
79
|
+
/** Serialize reconciliation of the one machine-global host tuple. This lock is
|
|
80
|
+
* deliberately separate from the immutable runtime-generation install lock. */
|
|
81
|
+
function withBrandedHostLock(run) {
|
|
82
|
+
const root = join(homedir(), CRTR_DIR_NAME);
|
|
83
|
+
const lockPath = join(root, 'host.lock');
|
|
84
|
+
mkdirSync(root, { recursive: true });
|
|
85
|
+
return withExclusiveDirectoryLock(lockPath, run, {
|
|
86
|
+
timeoutMs: HOST_LOCK_TIMEOUT_MS,
|
|
87
|
+
timeoutError: () => new Error(`timed out waiting for branded host lock: ${lockPath}`),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
/** macOS keys FDA grants on the Mach-O cdhash. A real developer identity
|
|
91
|
+
* keeps that hash stable across branded-host rebuilds; ad-hoc signing is the
|
|
92
|
+
* portable default for machines without a local signing certificate. */
|
|
93
|
+
export function codesignIdentitiesForBrandedHost(opts = {}) {
|
|
94
|
+
const configured = opts.configured?.trim();
|
|
95
|
+
if (configured)
|
|
96
|
+
return [configured];
|
|
97
|
+
const discovered = opts.discovered?.trim();
|
|
98
|
+
return [discovered || '-'];
|
|
99
|
+
}
|
|
100
|
+
function discoverCodesignIdentity() {
|
|
101
|
+
if (process.platform !== 'darwin')
|
|
102
|
+
return null;
|
|
103
|
+
const res = spawnSync('security', ['find-identity', '-v', '-p', 'codesigning'], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
104
|
+
if (res.status !== 0)
|
|
105
|
+
return null;
|
|
106
|
+
const stdout = (res.stdout ?? '').toString();
|
|
107
|
+
const identities = stdout
|
|
108
|
+
.split('\n')
|
|
109
|
+
.map((line) => line.match(/\)\s+[A-Fa-f0-9]{40}\s+"([^"]+)"/)?.[1] ?? null)
|
|
110
|
+
.filter((identity) => identity !== null);
|
|
111
|
+
return identities.find((identity) => identity.startsWith('Apple Development:'))
|
|
112
|
+
?? identities.find((identity) => identity.startsWith('Developer ID Application:'))
|
|
113
|
+
?? identities[0]
|
|
114
|
+
?? null;
|
|
115
|
+
}
|
|
116
|
+
function runCodesign(binPath, identity) {
|
|
117
|
+
const res = spawnSync('codesign', ['-s', identity, '--deep', '--force', binPath], { stdio: ['pipe', 'pipe', 'pipe'] });
|
|
118
|
+
return { ok: res.status === 0, stderr: (res.stderr ?? '').toString().trim() };
|
|
119
|
+
}
|
|
120
|
+
function signBrandedHost(binPath) {
|
|
121
|
+
if (process.platform !== 'darwin')
|
|
122
|
+
return;
|
|
123
|
+
const identities = codesignIdentitiesForBrandedHost({
|
|
124
|
+
configured: process.env['CRTR_CODESIGN_IDENTITY'],
|
|
125
|
+
discovered: discoverCodesignIdentity(),
|
|
126
|
+
});
|
|
127
|
+
let lastError = '';
|
|
128
|
+
for (const identity of identities) {
|
|
129
|
+
const res = runCodesign(binPath, identity);
|
|
130
|
+
if (res.ok)
|
|
131
|
+
return;
|
|
132
|
+
lastError = res.stderr;
|
|
133
|
+
}
|
|
134
|
+
if (lastError && !lastError.includes('valid on disk')) {
|
|
135
|
+
emitEvent({ event: 'host.codesign.failed', level: 'debug', error: lastError });
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`codesign failed for ${binPath}: ${lastError || 'codesign did not exit successfully'}`);
|
|
138
|
+
}
|
|
139
|
+
export function signBrandedHostArtifacts(binPath, libnodePath, sign = signBrandedHost) {
|
|
140
|
+
if (libnodePath !== null)
|
|
141
|
+
sign(libnodePath);
|
|
142
|
+
sign(binPath);
|
|
143
|
+
}
|
|
144
|
+
export function brandedHostWorks(binPath, timeoutMs = 5_000) {
|
|
145
|
+
try {
|
|
146
|
+
const res = spawnSync(binPath, ['-e', ''], {
|
|
147
|
+
stdio: 'ignore',
|
|
148
|
+
timeout: timeoutMs,
|
|
149
|
+
});
|
|
150
|
+
return res.status === 0;
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Ensure the branded host binary exists and matches the live node; return its
|
|
157
|
+
* absolute path. Idempotent and self-healing. Throws on a genuine build failure
|
|
158
|
+
* (disk full / permissions) — branding is load-bearing for FDA, so a failure is
|
|
159
|
+
* surfaced, never silently masked by falling back to `node`. */
|
|
160
|
+
export function ensureBrandedHost() {
|
|
161
|
+
return withBrandedHostLock(() => {
|
|
162
|
+
const hostDir = brandedHostDir();
|
|
163
|
+
const dst = brandedHostBin();
|
|
164
|
+
const src = realpathSync(process.execPath);
|
|
165
|
+
// If THIS process is already running as the branded host (the daemon or a
|
|
166
|
+
// broker spawning a child), don't rebuild from ourselves — we can't see the
|
|
167
|
+
// real node from here, and the live process proves the copy works. Drift is
|
|
168
|
+
// healed by the next real-`node` CLI invocation.
|
|
169
|
+
if (existsSync(dst) && realpathSync(dst) === src)
|
|
170
|
+
return dst;
|
|
171
|
+
const marker = readMarker();
|
|
172
|
+
const libnodeSrc = findLibnode(src);
|
|
173
|
+
const libnodeName = libnodeSrc !== null ? libnodeSrc.split('/').pop() : null;
|
|
174
|
+
const tupleExists = (candidate) => existsSync(dst) &&
|
|
175
|
+
candidate !== null &&
|
|
176
|
+
candidate.version === 2 &&
|
|
177
|
+
(candidate.libnode === null || existsSync(join(hostDir, candidate.libnode)));
|
|
178
|
+
const tupleMatches = (candidate) => tupleExists(candidate) && candidate.source === src && candidate.libnode === libnodeName;
|
|
179
|
+
if (tupleMatches(marker) && brandedHostWorks(dst))
|
|
180
|
+
return dst;
|
|
181
|
+
// Build and validate a complete tuple privately. Failures before publication
|
|
182
|
+
// leave any previous valid host untouched.
|
|
183
|
+
mkdirSync(hostDir, { recursive: true });
|
|
184
|
+
const stageDir = join(hostDir, `.reconcile.${process.pid}.${Math.random().toString(36).slice(2)}`);
|
|
185
|
+
const backupDir = join(stageDir, 'previous');
|
|
186
|
+
mkdirSync(stageDir);
|
|
187
|
+
let publicationStarted = false;
|
|
188
|
+
const previousTupleIsValid = tupleExists(marker) && brandedHostWorks(dst);
|
|
189
|
+
try {
|
|
190
|
+
const stageBin = join(stageDir, BRANDED_BIN_NAME);
|
|
191
|
+
const stageLibnode = libnodeName !== null ? join(stageDir, libnodeName) : null;
|
|
192
|
+
if (libnodeSrc !== null && stageLibnode !== null)
|
|
193
|
+
atomicCopy(libnodeSrc, stageLibnode);
|
|
194
|
+
atomicCopy(src, stageBin, 0o755);
|
|
195
|
+
signBrandedHostArtifacts(stageBin, stageLibnode);
|
|
196
|
+
if (!brandedHostWorks(stageBin)) {
|
|
197
|
+
throw new Error(`rebuilt branded host failed its startup check: ${stageBin}`);
|
|
198
|
+
}
|
|
199
|
+
if (previousTupleIsValid)
|
|
200
|
+
backupHostTuple(backupDir, dst, marker);
|
|
201
|
+
publicationStarted = true;
|
|
202
|
+
if (stageLibnode !== null)
|
|
203
|
+
renameSync(stageLibnode, join(hostDir, libnodeName));
|
|
204
|
+
renameSync(stageBin, dst);
|
|
205
|
+
atomicCopyMarker({ version: 2, source: src, libnode: libnodeName });
|
|
206
|
+
if (!tupleMatches(readMarker()) || !brandedHostWorks(dst)) {
|
|
207
|
+
throw new Error(`published branded host tuple failed validation: ${dst}`);
|
|
208
|
+
}
|
|
209
|
+
// Cleanup follows successful marker-last publication and validation.
|
|
210
|
+
if (marker?.libnode != null && marker.libnode !== libnodeName) {
|
|
211
|
+
try {
|
|
212
|
+
unlinkSync(join(hostDir, marker.libnode));
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
/* best-effort */
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return dst;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
if (publicationStarted && previousTupleIsValid)
|
|
222
|
+
restoreHostTuple(backupDir, dst, marker);
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
finally {
|
|
226
|
+
rmSync(stageDir, { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function atomicCopyMarker(marker) {
|
|
231
|
+
const path = markerPath();
|
|
232
|
+
const tmp = `${path}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`;
|
|
233
|
+
writeFileSync(tmp, JSON.stringify(marker));
|
|
234
|
+
renameSync(tmp, path);
|
|
235
|
+
}
|
|
236
|
+
function backupHostTuple(backupDir, binPath, marker) {
|
|
237
|
+
mkdirSync(backupDir);
|
|
238
|
+
linkSync(binPath, join(backupDir, BRANDED_BIN_NAME));
|
|
239
|
+
if (marker.libnode !== null)
|
|
240
|
+
linkSync(join(brandedHostDir(), marker.libnode), join(backupDir, marker.libnode));
|
|
241
|
+
writeFileSync(join(backupDir, '.source.json'), JSON.stringify(marker));
|
|
242
|
+
}
|
|
243
|
+
function restoreHostTuple(backupDir, binPath, marker) {
|
|
244
|
+
if (marker.libnode !== null)
|
|
245
|
+
renameSync(join(backupDir, marker.libnode), join(brandedHostDir(), marker.libnode));
|
|
246
|
+
renameSync(join(backupDir, BRANDED_BIN_NAME), binPath);
|
|
247
|
+
renameSync(join(backupDir, '.source.json'), markerPath());
|
|
248
|
+
if (!brandedHostWorks(binPath))
|
|
249
|
+
throw new Error(`failed to restore previous branded host tuple: ${binPath}`);
|
|
250
|
+
}
|
|
251
|
+
/** The path the engine (broker / daemon) should be launched from. On macOS in a
|
|
252
|
+
* compiled (production) build this is the validated branded host; otherwise —
|
|
253
|
+
* non-darwin (TCC is macOS-only), source/tsx (dev + the test suite: never copy
|
|
254
|
+
* 70 MB into the real ~/.crouter), or the explicit `CRTR_BRANDED_HOST=0`
|
|
255
|
+
* opt-out — it is the live node. */
|
|
256
|
+
export function hostExecPath() {
|
|
257
|
+
if (process.env['CRTR_BRANDED_HOST'] === '0')
|
|
258
|
+
return process.execPath;
|
|
259
|
+
if (process.platform !== 'darwin')
|
|
260
|
+
return process.execPath;
|
|
261
|
+
if (import.meta.url.endsWith('.ts'))
|
|
262
|
+
return process.execPath;
|
|
263
|
+
return ensureBrandedHost();
|
|
264
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type BrokerSignalCachedNode } from '../../../daemon/broker-signals/bus.js';
|
|
2
|
+
import type { BrokerExtensionStateDTO, BrokerGeneratedNameRequest, BrokerGeneratedNameResultDTO, BrokerModelCommitRequest, BrokerModelCommitResultDTO, BrokerParkActivityResultDTO, BrokerParkCompleteRequest, BrokerPersonaAckRequest, BrokerPersonaAckResultDTO, BrokerSessionBoundRequest, BrokerSessionBoundResultDTO, BrokerSettleDirective, BrokerSettleRequest, BrokerTelemetryRequest } from '../../../api/dto/broker-ops.js';
|
|
3
|
+
import type { AcknowledgeMailResultDTO, ClaimMailResultDTO, MailReceiptDTO } from '../../../api/dto/mail.js';
|
|
4
|
+
import type { BrokerFaultInputDTO, BrokerFaultRequest, BrokerFaultResultDTO, BrokerProviderRetryRequest, BrokerProviderRetryResultDTO, BrokerTurnRequest, BrokerTurnResultDTO, RecoveryStateDTO } from '../../../api/dto/recovery.js';
|
|
5
|
+
import type { RelaunchRootResultDTO } from '../../../api/dto/lifecycle.js';
|
|
6
|
+
import type { PushReportRequest } from '../../../api/dto/reports.js';
|
|
7
|
+
import type { BackgroundedJobDTO, BackgroundedJobRequest, DeliveryRequest, DeliveryResponse } from '../../../api/dto/delivery.js';
|
|
8
|
+
import type { NodeLogAppendResultDTO, NodeMessageBodyDTO, NodePushedFinalResultDTO, NodeTelemetryRecordRequest } from '../../../api/dto/node-records.js';
|
|
9
|
+
export declare function appendBrokerLog(nodeId: string, record: string): Promise<NodeLogAppendResultDTO>;
|
|
10
|
+
export declare function putBrokerTelemetry(nodeId: string, telemetry: Omit<NodeTelemetryRecordRequest, 'expected_execution_id'>): Promise<void>;
|
|
11
|
+
export declare function putBrokerRecap(nodeId: string, briefing: string): Promise<void>;
|
|
12
|
+
/** The body of a direct message the daemon spilled into this node's records. */
|
|
13
|
+
export declare function readBrokerPassiveMessage(nodeId: string, ref: string): Promise<NodeMessageBodyDTO>;
|
|
14
|
+
export declare function clearBrokerPushedFinal(nodeId: string): Promise<NodePushedFinalResultDTO>;
|
|
15
|
+
export declare function consumeBrokerPushedFinal(nodeId: string): Promise<NodePushedFinalResultDTO>;
|
|
16
|
+
export declare function startBrokerBashJob(nodeId: string, request: {
|
|
17
|
+
job_id: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
env: NodeJS.ProcessEnv;
|
|
20
|
+
}): Promise<{
|
|
21
|
+
pid: number;
|
|
22
|
+
}>;
|
|
23
|
+
/** Record a command the valve just backgrounded as this node's job object. */
|
|
24
|
+
export declare function recordBackgroundedJob(nodeId: string, jobId: string, request: BackgroundedJobRequest): Promise<BackgroundedJobDTO>;
|
|
25
|
+
export declare function stopBrokerBashJob(nodeId: string, jobId: string): Promise<void>;
|
|
26
|
+
export declare function brokerExecutionId(): string;
|
|
27
|
+
export declare function bindBrokerSession(nodeId: string, request: Omit<BrokerSessionBoundRequest, 'executionId'>): Promise<BrokerSessionBoundResultDTO>;
|
|
28
|
+
type BrokerInput<T> = T extends unknown ? Omit<T, 'expected_execution_id'> : never;
|
|
29
|
+
export declare function settleBroker(nodeId: string, request: BrokerInput<BrokerSettleRequest>): Promise<BrokerSettleDirective>;
|
|
30
|
+
export declare function completeBrokerPark(nodeId: string, request: BrokerInput<BrokerParkCompleteRequest>): Promise<BrokerSettleDirective>;
|
|
31
|
+
export declare function recordBrokerParkActivity(nodeId: string): Promise<BrokerParkActivityResultDTO>;
|
|
32
|
+
export declare function recordBrokerTelemetry(nodeId: string, request: Omit<BrokerTelemetryRequest, 'expected_execution_id'>): Promise<void>;
|
|
33
|
+
export declare function claimBrokerMail(nodeId: string, channel: 'wake' | 'passive' | 'deferred_run', reportBodies: Map<string, Promise<string>>): Promise<ClaimMailResultDTO>;
|
|
34
|
+
export declare function acknowledgeBrokerMail(nodeId: string, deliveries: MailReceiptDTO[]): Promise<AcknowledgeMailResultDTO>;
|
|
35
|
+
export declare function recordBrokerTurn(nodeId: string, request: Omit<BrokerTurnRequest, 'expected_execution_id'>): Promise<BrokerTurnResultDTO>;
|
|
36
|
+
export declare function mutateBrokerProviderRetry(nodeId: string, request: Omit<BrokerProviderRetryRequest, 'expected_execution_id'>): Promise<BrokerProviderRetryResultDTO>;
|
|
37
|
+
export declare function mutateBrokerFault(nodeId: string, request: Omit<BrokerFaultRequest, 'expected_execution_id'>): Promise<BrokerFaultResultDTO>;
|
|
38
|
+
export declare function brokerRecovery(nodeId: string): Promise<RecoveryStateDTO>;
|
|
39
|
+
/** Node-scoped fault writes are for non-broker clients such as attach transport.
|
|
40
|
+
* Broker-owned recovery mutations use the execution-fenced wrappers above. */
|
|
41
|
+
export declare function recordNodeFault(nodeId: string, input: BrokerFaultInputDTO): Promise<BrokerFaultResultDTO>;
|
|
42
|
+
export declare function clearNodeFault(nodeId: string, opts?: {
|
|
43
|
+
link?: BrokerFaultInputDTO['link'];
|
|
44
|
+
preserveEpisode?: boolean;
|
|
45
|
+
}): Promise<boolean>;
|
|
46
|
+
/** Fixed canvas projection for broker extension rendering. It deliberately
|
|
47
|
+
* exposes no generic node/database facade. */
|
|
48
|
+
export declare function commitBrokerModel(nodeId: string, request: BrokerInput<BrokerModelCommitRequest>): Promise<BrokerModelCommitResultDTO>;
|
|
49
|
+
export declare function brokerExtensionState(nodeId: string): Promise<BrokerExtensionStateDTO>;
|
|
50
|
+
/** Only node-owned fields are cached. A disconnected or stopped stream cannot
|
|
51
|
+
* guarantee freshness, so events at those boundaries read the daemon instead. */
|
|
52
|
+
export declare function cachedBrokerNode(nodeId: string): Promise<BrokerSignalCachedNode>;
|
|
53
|
+
/** The node's one delivery route: every rule-driven document delivery the
|
|
54
|
+
* broker applies, recorded by crtrd in the same request. */
|
|
55
|
+
export declare function brokerDelivery(nodeId: string, request: BrokerInput<DeliveryRequest>): Promise<DeliveryResponse>;
|
|
56
|
+
export declare function commitBrokerGeneratedName(nodeId: string, request: BrokerInput<BrokerGeneratedNameRequest>): Promise<BrokerGeneratedNameResultDTO>;
|
|
57
|
+
export declare function commitBrokerPersonaAck(nodeId: string, request: BrokerInput<BrokerPersonaAckRequest>): Promise<BrokerPersonaAckResultDTO>;
|
|
58
|
+
/** Reports still use the canonical daemon report handler; this broker adapter
|
|
59
|
+
* only preserves terminal structured output's idempotent finalization result. */
|
|
60
|
+
export declare function publishBrokerReport(nodeId: string, request: PushReportRequest): Promise<void>;
|
|
61
|
+
/** Root `/new` remains on the dedicated relaunch operation because it launches
|
|
62
|
+
* a replacement broker and tears down this broker. Callers must not await it
|
|
63
|
+
* from Pi's session-start dispatch. */
|
|
64
|
+
export declare function relaunchBrokerRoot(nodeId: string): Promise<RelaunchRootResultDTO>;
|
|
65
|
+
export {};
|