@ours.network/fleet 0.13.2 → 0.14.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/README.md +93 -60
- package/dist/briefing.js +15 -12
- package/dist/config.d.ts +10 -0
- package/dist/config.js +21 -2
- package/dist/docs.d.ts +1 -1
- package/dist/docs.js +35 -45
- package/dist/loops/manager.d.ts +1 -0
- package/dist/loops/manager.js +23 -1
- package/dist/owner-channel/channel.d.ts +52 -0
- package/dist/owner-channel/channel.js +403 -62
- package/dist/owner-channel/commands.d.ts +79 -0
- package/dist/owner-channel/commands.js +183 -0
- package/dist/owner-channel/notices.d.ts +7 -0
- package/dist/owner-channel/notices.js +20 -0
- package/dist/owner-channel/state.d.ts +45 -0
- package/dist/owner-channel/state.js +229 -19
- package/dist/owner-channel/tasks.js +7 -4
- package/dist/runner.js +2 -0
- package/dist/session/acp.d.ts +3 -0
- package/dist/session/acp.js +24 -1
- package/package.json +1 -1
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SessionEvent, SessionSnapshot } from '../session/types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Fleet-level effects a deterministic owner command may trigger. Production
|
|
4
|
+
* uses the detached CLI (`fleetCliOps`); tests inject fakes so no command can
|
|
5
|
+
* ever bounce a real service from the suite.
|
|
6
|
+
*/
|
|
7
|
+
export interface OwnerFleetOps {
|
|
8
|
+
/** `ours-fleet restart` (keep) or `ours-fleet force-restart` (fresh) of this role. */
|
|
9
|
+
restart(mode: 'keep' | 'fresh'): Promise<void>;
|
|
10
|
+
/** `ours-fleet ls` output. */
|
|
11
|
+
list(): Promise<string>;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The narrow capability surface a command executor sees. Everything here is
|
|
15
|
+
* already scoped to the one role whose channel received the message; commands
|
|
16
|
+
* cannot name another agent or another recipient.
|
|
17
|
+
*/
|
|
18
|
+
export interface OwnerCommandContext {
|
|
19
|
+
role: string;
|
|
20
|
+
/** Harness id of the role (e.g. 'claude-code', 'codex'); gates forwarding. */
|
|
21
|
+
harness: string;
|
|
22
|
+
version: string;
|
|
23
|
+
snapshot(): SessionSnapshot;
|
|
24
|
+
interrupt(): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Deliver raw slash text to the agent harness. Only commands the bundled
|
|
27
|
+
* ACP adapter for `harness` verifiably executes locally may be forwarded
|
|
28
|
+
* (see HARNESS_LOCAL_COMMANDS); anything else would reach the model as an
|
|
29
|
+
* ordinary prompt. The channel sends the acceptance and outcome notices
|
|
30
|
+
* itself.
|
|
31
|
+
*/
|
|
32
|
+
runHarnessCommand(command: string): Promise<void>;
|
|
33
|
+
restart(mode: 'keep' | 'fresh'): Promise<void>;
|
|
34
|
+
fleetList(): Promise<string>;
|
|
35
|
+
recentEvents(limit: number): SessionEvent[];
|
|
36
|
+
readWorklogTail(maxChars: number): Promise<string | undefined>;
|
|
37
|
+
reply(text: string): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export interface OwnerCommand {
|
|
40
|
+
/** Primary name without the leading slash. */
|
|
41
|
+
name: string;
|
|
42
|
+
aliases?: string[];
|
|
43
|
+
/** Shown in help; defaults to `/<name>`. */
|
|
44
|
+
usage?: string;
|
|
45
|
+
/** One-line description shown in help. */
|
|
46
|
+
summary: string;
|
|
47
|
+
execute(ctx: OwnerCommandContext, args: string): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Commands each harness's bundled ACP adapter verifiably executes locally,
|
|
51
|
+
* pinned by test/acp-adapter-commands.test.ts against the shipped adapter
|
|
52
|
+
* artifacts. claude-agent-acp routes slash commands into the Claude SDK,
|
|
53
|
+
* which runs its builtins (/clear, /compact, /model) without a model turn;
|
|
54
|
+
* codex-acp intercepts only /compact — /clear and /model are not builtins
|
|
55
|
+
* and would fall through into sendPrompt, i.e. reach the model as an
|
|
56
|
+
* ordinary prompt. Unlisted harnesses forward nothing.
|
|
57
|
+
*/
|
|
58
|
+
export declare const HARNESS_LOCAL_COMMANDS: Record<string, readonly string[]>;
|
|
59
|
+
/**
|
|
60
|
+
* The single source of truth for the deterministic owner-channel command set:
|
|
61
|
+
* /help renders exactly this table, so adding an entry here is the whole
|
|
62
|
+
* registration step for a new command.
|
|
63
|
+
*/
|
|
64
|
+
export declare const ownerCommands: OwnerCommand[];
|
|
65
|
+
/** Trimmed slash-prefixed text is a command attempt and is never forwarded. */
|
|
66
|
+
export declare const isOwnerCommandText: (text: string) => boolean;
|
|
67
|
+
export declare function ownerCommandHelp(error?: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* Execute one authenticated owner command. `text` must already be trimmed,
|
|
70
|
+
* slash-prefixed, and from an authorized owner CID — the channel enforces the
|
|
71
|
+
* authority boundary before dispatch ever sees the message.
|
|
72
|
+
*/
|
|
73
|
+
export declare function dispatchOwnerCommand(text: string, ctx: OwnerCommandContext): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Production fleet effects: the detached ours-fleet CLI. The restart child is
|
|
76
|
+
* detached and unreferenced because a successful restart kills this very
|
|
77
|
+
* process; the reply and the durable wire record must already be on disk.
|
|
78
|
+
*/
|
|
79
|
+
export declare function fleetCliOps(role: string, configPath?: string): OwnerFleetOps;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { execFile, spawn } from 'node:child_process';
|
|
2
|
+
import { ownerNotices } from './notices.js';
|
|
3
|
+
/** A malformed invocation; the dispatcher answers it with annotated help. */
|
|
4
|
+
class OwnerCommandUsageError extends Error {
|
|
5
|
+
}
|
|
6
|
+
const MODEL_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
|
7
|
+
const REPLY_MAX_CHARS = 3_500;
|
|
8
|
+
const strip = (value, max) => String(value).replace(/[\u0000-\u001f\u007f]/g, ' ').slice(0, max);
|
|
9
|
+
/** Like `strip`, but keeps newlines so multi-line listings stay readable. */
|
|
10
|
+
const stripMultiline = (value, max) => String(value).replace(/[\u0000-\u0009\u000b-\u001f\u007f]/g, ' ').slice(0, max);
|
|
11
|
+
/**
|
|
12
|
+
* Commands each harness's bundled ACP adapter verifiably executes locally,
|
|
13
|
+
* pinned by test/acp-adapter-commands.test.ts against the shipped adapter
|
|
14
|
+
* artifacts. claude-agent-acp routes slash commands into the Claude SDK,
|
|
15
|
+
* which runs its builtins (/clear, /compact, /model) without a model turn;
|
|
16
|
+
* codex-acp intercepts only /compact — /clear and /model are not builtins
|
|
17
|
+
* and would fall through into sendPrompt, i.e. reach the model as an
|
|
18
|
+
* ordinary prompt. Unlisted harnesses forward nothing.
|
|
19
|
+
*/
|
|
20
|
+
export const HARNESS_LOCAL_COMMANDS = {
|
|
21
|
+
'claude-code': ['clear', 'compact', 'model'],
|
|
22
|
+
codex: ['compact'],
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Forward raw slash text to the harness only when the bundled adapter for
|
|
26
|
+
* this role's harness verifiably executes it locally; otherwise answer with
|
|
27
|
+
* a truthful refusal so the text can never reach the model as a prompt.
|
|
28
|
+
*/
|
|
29
|
+
const forwardHarnessCommand = (ctx, raw) => {
|
|
30
|
+
const name = raw.slice(1).split(/\s+/, 1)[0];
|
|
31
|
+
if (!(HARNESS_LOCAL_COMMANDS[ctx.harness] ?? []).includes(name))
|
|
32
|
+
return ctx.reply(ownerNotices.commandUnsupported(`/${name}`, ctx.harness));
|
|
33
|
+
return ctx.runHarnessCommand(raw);
|
|
34
|
+
};
|
|
35
|
+
/** Keep the LAST characters — tails are more useful than heads for logs. */
|
|
36
|
+
const tail = (value, max) => {
|
|
37
|
+
const points = Array.from(value);
|
|
38
|
+
return points.length <= max ? value : `…${points.slice(-max).join('')}`;
|
|
39
|
+
};
|
|
40
|
+
const noArgs = (usage, run) => async (ctx, args) => {
|
|
41
|
+
if (args)
|
|
42
|
+
throw new OwnerCommandUsageError(`${usage} takes no arguments`);
|
|
43
|
+
await run(ctx);
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The single source of truth for the deterministic owner-channel command set:
|
|
47
|
+
* /help renders exactly this table, so adding an entry here is the whole
|
|
48
|
+
* registration step for a new command.
|
|
49
|
+
*/
|
|
50
|
+
export const ownerCommands = [
|
|
51
|
+
{
|
|
52
|
+
name: 'help', aliases: ['commands'],
|
|
53
|
+
summary: 'list all deterministic owner-channel commands (alias: /commands)',
|
|
54
|
+
execute: async (ctx) => ctx.reply(ownerCommandHelp()),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'status', summary: "report the agent's session state",
|
|
58
|
+
execute: noArgs('/status', async (ctx) => ctx.reply(ownerNotices.status(ctx.role, ctx.snapshot()))),
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
name: 'interrupt', summary: "cancel the agent's active turn",
|
|
62
|
+
execute: noArgs('/interrupt', async (ctx) => {
|
|
63
|
+
try {
|
|
64
|
+
await ctx.interrupt();
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return ctx.reply(ownerNotices.interruptFailed(ctx.role));
|
|
68
|
+
}
|
|
69
|
+
await ctx.reply(ownerNotices.interrupted(ctx.role));
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'clear', summary: "clear the agent's session context",
|
|
74
|
+
execute: noArgs('/clear', ctx => forwardHarnessCommand(ctx, '/clear')),
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'compact', summary: "compact the agent's session context",
|
|
78
|
+
execute: noArgs('/compact', ctx => forwardHarnessCommand(ctx, '/compact')),
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'model', usage: '/model <model-id>',
|
|
82
|
+
summary: 'switch the model the agent runs on',
|
|
83
|
+
execute: async (ctx, args) => {
|
|
84
|
+
if (!args)
|
|
85
|
+
throw new OwnerCommandUsageError('usage: /model <model-id>');
|
|
86
|
+
if (!MODEL_ID.test(args))
|
|
87
|
+
throw new OwnerCommandUsageError('model id must be alphanumeric with . _ : - only');
|
|
88
|
+
await forwardHarnessCommand(ctx, `/model ${args}`);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'restart', summary: 'restart the agent, resuming its context',
|
|
93
|
+
execute: noArgs('/restart', ctx => ctx.restart('keep')),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'force-restart', summary: 'restart the agent FRESH (context wiped)',
|
|
97
|
+
execute: noArgs('/force-restart', ctx => ctx.restart('fresh')),
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: 'ls', summary: 'list running fleet sessions',
|
|
101
|
+
execute: noArgs('/ls', async (ctx) => ctx.reply(`📊 Fleet sessions:\n${tail(stripMultiline(await ctx.fleetList(), 10_000), REPLY_MAX_CHARS)}`)),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'peek', summary: 'summarize recent session activity (event shapes only, no content)',
|
|
105
|
+
execute: noArgs('/peek', async (ctx) => {
|
|
106
|
+
const lines = ctx.recentEvents(20).map(event => ['·', event.kind,
|
|
107
|
+
...(event.title !== undefined ? [strip(event.title, 80)] : []),
|
|
108
|
+
...(event.status !== undefined ? [`(${strip(event.status, 40)})`] : []),
|
|
109
|
+
...(event.stopReason !== undefined ? [`(${strip(event.stopReason, 40)})`] : []),
|
|
110
|
+
].join(' '));
|
|
111
|
+
await ctx.reply(lines.length
|
|
112
|
+
? tail(`📊 Recent activity for ${ctx.role}:\n${lines.join('\n')}`, REPLY_MAX_CHARS)
|
|
113
|
+
: `📊 No recent session activity recorded for ${ctx.role}.`);
|
|
114
|
+
}),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'worklog', summary: "tail the agent's worklog",
|
|
118
|
+
execute: noArgs('/worklog', async (ctx) => {
|
|
119
|
+
const worklog = await ctx.readWorklogTail(REPLY_MAX_CHARS);
|
|
120
|
+
await ctx.reply(worklog
|
|
121
|
+
? `📊 Worklog tail for ${ctx.role}:\n${worklog}`
|
|
122
|
+
: `ℹ️ No worklog found for ${ctx.role}.`);
|
|
123
|
+
}),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'version', summary: 'report the fleet version',
|
|
127
|
+
execute: noArgs('/version', async (ctx) => ctx.reply(`ℹ️ ours-fleet ${ctx.version}`)),
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
/** Trimmed slash-prefixed text is a command attempt and is never forwarded. */
|
|
131
|
+
export const isOwnerCommandText = (text) => text.trim().startsWith('/');
|
|
132
|
+
export function ownerCommandHelp(error) {
|
|
133
|
+
const table = ownerCommands
|
|
134
|
+
.map(command => `${command.usage ?? `/${command.name}`} — ${command.summary}`)
|
|
135
|
+
.join('\n');
|
|
136
|
+
return `${error ? `⚠️ ${error}\n\n` : ''}🧭 Deterministic owner-channel commands `
|
|
137
|
+
+ '(handled by fleet; never sent to the agent as a prompt):\n'
|
|
138
|
+
+ `${table}\n`
|
|
139
|
+
+ 'Messages without a leading "/" reach the agent unchanged. '
|
|
140
|
+
+ 'Unknown or malformed commands return this help.';
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Execute one authenticated owner command. `text` must already be trimmed,
|
|
144
|
+
* slash-prefixed, and from an authorized owner CID — the channel enforces the
|
|
145
|
+
* authority boundary before dispatch ever sees the message.
|
|
146
|
+
*/
|
|
147
|
+
export async function dispatchOwnerCommand(text, ctx) {
|
|
148
|
+
const trimmed = text.trim();
|
|
149
|
+
const token = trimmed.split(/\s+/, 1)[0];
|
|
150
|
+
const name = token.slice(1).toLowerCase();
|
|
151
|
+
const args = trimmed.slice(token.length).trim();
|
|
152
|
+
const command = ownerCommands.find(entry => entry.name === name || entry.aliases?.includes(name));
|
|
153
|
+
if (!command)
|
|
154
|
+
return ctx.reply(ownerCommandHelp(`unknown command ${strip(token, 60)}`));
|
|
155
|
+
try {
|
|
156
|
+
await command.execute(ctx, args);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error instanceof OwnerCommandUsageError)
|
|
160
|
+
return ctx.reply(ownerCommandHelp(error.message));
|
|
161
|
+
// The failure notice carries no internal detail; the channel logs it.
|
|
162
|
+
await ctx.reply(ownerNotices.commandFailed(command.usage ?? `/${command.name}`));
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Production fleet effects: the detached ours-fleet CLI. The restart child is
|
|
168
|
+
* detached and unreferenced because a successful restart kills this very
|
|
169
|
+
* process; the reply and the durable wire record must already be on disk.
|
|
170
|
+
*/
|
|
171
|
+
export function fleetCliOps(role, configPath) {
|
|
172
|
+
const cli = (args) => [process.argv[1], ...args, ...(configPath ? ['-c', configPath] : [])];
|
|
173
|
+
return {
|
|
174
|
+
restart: mode => new Promise((resolve, reject) => {
|
|
175
|
+
const child = spawn(process.execPath, cli([mode === 'fresh' ? 'force-restart' : 'restart', role]), { detached: true, stdio: 'ignore' });
|
|
176
|
+
child.once('error', reject);
|
|
177
|
+
child.once('spawn', () => { child.unref(); resolve(); });
|
|
178
|
+
}),
|
|
179
|
+
list: () => new Promise((resolve, reject) => {
|
|
180
|
+
execFile(process.execPath, [process.argv[1], 'ls'], { timeout: 15_000, maxBuffer: 256 * 1024 }, (error, stdout) => error ? reject(error) : resolve(String(stdout).trim()));
|
|
181
|
+
}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
@@ -9,12 +9,19 @@ export declare const ownerNotices: {
|
|
|
9
9
|
status: (role: string, snapshot: SessionSnapshot) => string;
|
|
10
10
|
interrupted: (role: string) => string;
|
|
11
11
|
interruptFailed: (role: string) => string;
|
|
12
|
+
commandStarted: (command: string) => string;
|
|
13
|
+
commandOutcome: (command: string, outcome: TurnOutcome, output?: string) => string;
|
|
14
|
+
commandFailed: (command: string) => string;
|
|
15
|
+
commandUnsupported: (command: string, harness: string) => string;
|
|
16
|
+
restarting: (role: string, command: string, mode: "keep" | "fresh") => string;
|
|
12
17
|
attachmentRejected: (reason: string) => string;
|
|
13
18
|
attachmentFailed: () => string;
|
|
14
19
|
deliveryFailed: (role: string) => string;
|
|
15
20
|
progress: (elapsedMs: number, phase: OwnerProgressPhase, started: number, completed: number, activityUpdates?: number) => string;
|
|
16
21
|
authoredUpdate: (phase: OwnerUpdatePhase, message: string) => string;
|
|
17
22
|
taskReport: (phase: OwnerTaskPhase, message: string) => string;
|
|
23
|
+
relayQueued: () => string;
|
|
24
|
+
relayRefused: (reason: string) => string;
|
|
18
25
|
completedWithoutText: () => string;
|
|
19
26
|
terminal: (outcome: TurnOutcome) => "✅ Request completed." | "🛑 Request was cancelled before completion." | "⚠️ The agent declined this request." | "⚠️ Request failed before completion." | "⚠️ Request ended without a confirmed completion.";
|
|
20
27
|
chunk: (part: number, total: number) => string;
|
|
@@ -22,6 +22,23 @@ export const ownerNotices = {
|
|
|
22
22
|
status: (role, snapshot) => `📊 ${role} status: ${snapshot.readiness}; session is ${snapshot.alive ? 'online' : 'offline'}.`,
|
|
23
23
|
interrupted: (role) => `🛑 Interrupt sent to ${role}'s active turn.`,
|
|
24
24
|
interruptFailed: (role) => `⚠️ Could not interrupt ${role}'s active turn.`,
|
|
25
|
+
commandStarted: (command) => `⏳ Running ${command} — the result will follow in this channel.`,
|
|
26
|
+
commandOutcome: (command, outcome, output) => {
|
|
27
|
+
switch (outcome) {
|
|
28
|
+
case 'completed': return `✅ ${command} completed.${output ? `\n${output}` : ''}`;
|
|
29
|
+
case 'cancelled': return `🛑 ${command} was cancelled before completion.`;
|
|
30
|
+
case 'refused': return `⚠️ ${command} was declined by the agent harness.`;
|
|
31
|
+
case 'failed': return `⚠️ ${command} failed before completion.`;
|
|
32
|
+
case 'inconclusive': return `⚠️ ${command} ended without a confirmed completion.`;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
commandFailed: (command) => `⚠️ ${command} could not be executed.`,
|
|
36
|
+
commandUnsupported: (command, harness) => `⚠️ ${command} is not supported on the '${harness}' harness: its bundled ACP `
|
|
37
|
+
+ 'adapter does not execute it locally, so forwarding it would deliver the '
|
|
38
|
+
+ 'text to the model as an ordinary prompt. Nothing was forwarded.',
|
|
39
|
+
restarting: (role, command, mode) => `ℹ️ ${command} accepted — restarting ${role} ${mode === 'fresh'
|
|
40
|
+
? 'FRESH (context wiped)' : '(context resumes)'}. `
|
|
41
|
+
+ 'The channel goes quiet during the restart and resumes when the agent is back.',
|
|
25
42
|
attachmentRejected: (reason) => `⚠️ Attachment rejected: ${reason}.`,
|
|
26
43
|
attachmentFailed: () => '⚠️ Could not securely retrieve or admit this attachment request.',
|
|
27
44
|
deliveryFailed: (role) => `⚠️ Could not deliver this request to ${role}.`,
|
|
@@ -52,6 +69,9 @@ export const ownerNotices = {
|
|
|
52
69
|
case 'blocked': return `🚧 Follow-up blocked: ${message}`;
|
|
53
70
|
}
|
|
54
71
|
},
|
|
72
|
+
relayQueued: () => 'ℹ️ No owner has contacted this channel yet, so this message cannot be routed. '
|
|
73
|
+
+ 'It stays queued and will be relayed after the first owner message arrives.',
|
|
74
|
+
relayRefused: (reason) => `⚠️ This message was not relayed to an owner: ${reason}.`,
|
|
55
75
|
completedWithoutText: () => '✅ Request completed, but the agent returned no text.',
|
|
56
76
|
terminal: (outcome) => {
|
|
57
77
|
switch (outcome) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/** A send whose (dedupe-scoped) digest was already recorded: it must not repeat. */
|
|
2
|
+
export declare class DuplicateSendError extends Error {
|
|
3
|
+
}
|
|
1
4
|
/** Durable bounded dedupe containing wire IDs only — never message or reply plaintext. */
|
|
2
5
|
export declare class OwnerChannelState {
|
|
3
6
|
private readonly path;
|
|
@@ -8,6 +11,44 @@ export declare class OwnerChannelState {
|
|
|
8
11
|
has(wireId: string): boolean;
|
|
9
12
|
remember(wireId: string): void;
|
|
10
13
|
}
|
|
14
|
+
export type OwnerConversationRouteBasis = 'last-inbound' | 'sole-owner';
|
|
15
|
+
interface OwnerProactiveSend {
|
|
16
|
+
id: string;
|
|
17
|
+
contact: string;
|
|
18
|
+
digest: string;
|
|
19
|
+
at: number;
|
|
20
|
+
status: 'sending' | 'delivered' | 'uncertain';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Durable destination history for unscoped owner messages. It stores only
|
|
24
|
+
* authenticated CIDs, wire IDs, timestamps and content digests; never bodies,
|
|
25
|
+
* filenames or display names. A pre-send marker prevents blind replay after a
|
|
26
|
+
* crash or transport ambiguity.
|
|
27
|
+
*/
|
|
28
|
+
export declare class OwnerConversationState {
|
|
29
|
+
private readonly path;
|
|
30
|
+
private conversations;
|
|
31
|
+
private sends;
|
|
32
|
+
private corruptReason?;
|
|
33
|
+
constructor(path: string);
|
|
34
|
+
integrity(): {
|
|
35
|
+
ok: boolean;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
recordInbound(contact: string, wireId: string, now?: number): void;
|
|
39
|
+
remove(contact: string): void;
|
|
40
|
+
route(effective: Set<string>): {
|
|
41
|
+
contact: string;
|
|
42
|
+
basis: OwnerConversationRouteBasis;
|
|
43
|
+
};
|
|
44
|
+
beginSend(contact: string, digest: string, now?: number, minIntervalMs?: number, dedupe?: 'contact' | 'all'): OwnerProactiveSend;
|
|
45
|
+
finishSend(id: string, status: 'delivered' | 'uncertain'): void;
|
|
46
|
+
private mutate;
|
|
47
|
+
private persist;
|
|
48
|
+
private assertHealthy;
|
|
49
|
+
private validConversation;
|
|
50
|
+
private validSend;
|
|
51
|
+
}
|
|
11
52
|
export type OwnerSource = 'baseline' | 'dynamic';
|
|
12
53
|
export interface OwnerEntry {
|
|
13
54
|
cid: string;
|
|
@@ -36,9 +77,13 @@ export declare class OwnerAuthorizationState {
|
|
|
36
77
|
entries(): OwnerEntry[];
|
|
37
78
|
authorize(cid: string): OwnerEntry;
|
|
38
79
|
revoke(cid: string): OwnerEntry;
|
|
80
|
+
private inBaseline;
|
|
81
|
+
private hasCanonical;
|
|
82
|
+
private deleteCanonical;
|
|
39
83
|
private assertHealthy;
|
|
40
84
|
private record;
|
|
41
85
|
private snapshot;
|
|
42
86
|
private restore;
|
|
43
87
|
private persist;
|
|
44
88
|
}
|
|
89
|
+
export {};
|