@scp-dev/core 0.2.0 → 0.3.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/dist/index.d.ts +30 -8
- package/dist/index.js +60 -23
- package/package.json +1 -1
- package/src/core.ts +79 -22
- package/src/protocol.ts +12 -3
package/dist/index.d.ts
CHANGED
|
@@ -404,7 +404,7 @@ type DataClass = "public" | "private";
|
|
|
404
404
|
type Presence = "online" | "idle" | "offline";
|
|
405
405
|
type StatusState = "working" | "waiting" | "blocked" | "done";
|
|
406
406
|
/** how an idle agent is woken by a message: a channel push, the pi extension, the OpenCode plugin's waiter or codex app-server; `none` reads at its next turn */
|
|
407
|
-
type Wake = "channels" | "pi" | "opencode-plugin" | "codex-app-server" | "none";
|
|
407
|
+
type Wake = "channels" | "pi" | "opencode-plugin" | "codex-app-server" | "claude-inbox" | "none";
|
|
408
408
|
declare const WAKES: readonly Wake[];
|
|
409
409
|
type ActivityKind = "session_start" | "tool" | "stop" | "note" | "session_end";
|
|
410
410
|
declare const ACTIVITY_KINDS: readonly ActivityKind[];
|
|
@@ -658,6 +658,10 @@ interface Envelope {
|
|
|
658
658
|
expires_at: string;
|
|
659
659
|
/** a request whose sender holds the recipient's stop until it replies */
|
|
660
660
|
require_reply?: true;
|
|
661
|
+
/** a broker-issued request to link under its sender */
|
|
662
|
+
link?: {
|
|
663
|
+
parent: string;
|
|
664
|
+
};
|
|
661
665
|
/** set by inbox on a message a background hook already showed (and marked read), whose text may reach the agent only after this call */
|
|
662
666
|
shown_at?: string;
|
|
663
667
|
/**
|
|
@@ -769,6 +773,12 @@ interface SendResult {
|
|
|
769
773
|
held?: JudgeHeld;
|
|
770
774
|
redacted?: JudgeRedacted;
|
|
771
775
|
}
|
|
776
|
+
interface AdoptResult {
|
|
777
|
+
endpoint: EndpointView;
|
|
778
|
+
parent: string;
|
|
779
|
+
requested?: string;
|
|
780
|
+
held?: JudgeHeld;
|
|
781
|
+
}
|
|
772
782
|
/**
|
|
773
783
|
* a message the caller sent and, per recipient, when it read the message (acked), or when a background hook delivered it (acked by that
|
|
774
784
|
* showing; the agent gets it with its next request), and when a hook or channel showed it: ISO times, or null
|
|
@@ -792,7 +802,7 @@ interface AuditRow {
|
|
|
792
802
|
detail: Record<string, unknown>;
|
|
793
803
|
}
|
|
794
804
|
/** fields only the server sets: a client that sends them gets invalid_params */
|
|
795
|
-
declare const SERVER_FIELDS: readonly ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation"];
|
|
805
|
+
declare const SERVER_FIELDS: readonly ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation", "link"];
|
|
796
806
|
|
|
797
807
|
/**
|
|
798
808
|
* `*`, `ep:<handle>` (that one agent), `handle:<glob>`, `harness:<name>`, `kind:<agent|human>` or `label:<key>=<value>`, plus the scope selectors: `tree:self|root|parent|siblings|children`,
|
|
@@ -1133,7 +1143,11 @@ interface MessageRow {
|
|
|
1133
1143
|
expires_at: number;
|
|
1134
1144
|
sender_tty: number | null;
|
|
1135
1145
|
require_reply: number | null;
|
|
1146
|
+
link_parent: string | null;
|
|
1136
1147
|
}
|
|
1148
|
+
type AdoptSend = (from: string, params: SendParams, origin: {
|
|
1149
|
+
link: "session" | "self";
|
|
1150
|
+
}) => Promise<SendResult>;
|
|
1137
1151
|
interface CoreOptions {
|
|
1138
1152
|
/** the local user's address part; `me` until a login names one */
|
|
1139
1153
|
user?: string;
|
|
@@ -1384,16 +1398,23 @@ declare class RelayCore {
|
|
|
1384
1398
|
* when given, renames it in place ({@link rename}'s rules). Nothing is kept when the rename is refused
|
|
1385
1399
|
*/
|
|
1386
1400
|
joinSession(claim: Claim, handle?: unknown, under?: unknown): EndpointView;
|
|
1387
|
-
/**
|
|
1388
|
-
* links a local root agent under `parent`, as a spawned worker sits: in the parent's tree one level down, under the policy's child scope.
|
|
1389
|
-
* Only the human at a terminal links another session, or an agent from its own shell with a terminal attached, under itself by default.
|
|
1390
|
-
* A session links itself with {@link joinSession}'s `under`
|
|
1391
|
-
*/
|
|
1401
|
+
/** the human links a local root directly; an agent asks it to consent through the normal send path */
|
|
1392
1402
|
adopt(actor: ScopeActor, params: {
|
|
1393
1403
|
endpoint: unknown;
|
|
1394
1404
|
parent?: unknown;
|
|
1395
1405
|
}, options?: {
|
|
1396
1406
|
tty?: boolean;
|
|
1407
|
+
}): AdoptResult;
|
|
1408
|
+
adopt(actor: ScopeActor, params: {
|
|
1409
|
+
endpoint: unknown;
|
|
1410
|
+
parent?: unknown;
|
|
1411
|
+
}, options: {
|
|
1412
|
+
tty?: boolean;
|
|
1413
|
+
send: AdoptSend;
|
|
1414
|
+
}): AdoptResult | Promise<AdoptResult>;
|
|
1415
|
+
/** accepts a broker-issued request delivered to the caller's own session */
|
|
1416
|
+
linkAccept(actor: ScopeActor, params: {
|
|
1417
|
+
id: unknown;
|
|
1397
1418
|
}): {
|
|
1398
1419
|
endpoint: EndpointView;
|
|
1399
1420
|
parent: string;
|
|
@@ -1459,6 +1480,7 @@ declare class RelayCore {
|
|
|
1459
1480
|
send(from: string, params: SendParams, origin?: {
|
|
1460
1481
|
tty?: boolean;
|
|
1461
1482
|
unverified?: boolean;
|
|
1483
|
+
link?: "session" | "self";
|
|
1462
1484
|
}): SendResult;
|
|
1463
1485
|
/** a local agent recipient's {@link Reach} for a send from `sender`; none for a human, the relay or another machine's row */
|
|
1464
1486
|
private reach;
|
|
@@ -2534,4 +2556,4 @@ declare function sha256Hex(text: string): string;
|
|
|
2534
2556
|
/** the UTF-8 byte length of text, the unit every byte limit counts in */
|
|
2535
2557
|
declare function utf8Bytes(text: string): number;
|
|
2536
2558
|
|
|
2537
|
-
export { ACCOUNT_HANDLE, ACTIVITY_KINDS, type ActivityKind, type ActivityRow, type Admission, type AskResult, type Attachment, type AuditRow, BROKER_CAPABILITIES, type BreakerAdmission, CAPABILITIES, CLI_NAME, type Claim, type CoreOptions, type CrossAccountGate, type CrossAccountLink, DEFAULT_IMPLEMENTATION_PREFIXES, DEFAULT_JUDGE_BUDGET, DEFAULT_JUDGE_EFFORT, type DataClass, ERROR_CODES, type EndpointKind, type EndpointRow, type EndpointView, type Envelope, type ErrorCode, FakeJudgeClient, GENERIC_HARNESSES, GONE_PREFIX, GROUP_NAME, type GateContext, type GateVerdict, HANDBACK_CUT, HUMAN_HANDLE, HttpJudgeClient, type HttpJudgeOptions, JUDGE_BREAKER_DEFAULTS, JUDGE_CATEGORIES, JUDGE_EFFORTS, JUDGE_FALLBACK_NOTE, JUDGE_MAX_OUTPUT_TOKENS, JUDGE_PROMPT_VERSION, JUDGE_PROVIDERS, JUDGE_VERDICTS, type JudgeAuditDetail, type JudgeAuditEvent, type JudgeBoundary, JudgeBreaker, type JudgeBreakerOptions, type JudgeBudget, type JudgeCategory, type JudgeClient, type JudgeContext, type JudgeEffort, type JudgeHeld, type JudgeModelConfig, type JudgePolicy, type JudgeProvider, JudgeProviderError, type JudgeRedacted, type JudgeSide, type JudgeStats, type JudgeVerdict, LIMITS, MESSAGE_KINDS, type MessageKind, type MessageReceipts, type MessageRow, OMITTED, PASS_GATE, PEEK, PREAMBLE, PREAMBLE_BRIEF, PRESET_LISTS, PROTOCOL, type PeekLevel, type PeekResult, type PeekSeen, type PeekState, type PeekTurn, type PeerView, type PiiHit, type Policy, type PolicyDefaults, type PolicyGroup, type PolicyRule, type PolicySubject, type Prefiltered, type Presence, REDACTION_KINDS, type Reach, type Redaction, RelayCore, RelayError, type RunJudgeInput, SCOPE_PRESETS, SERVER_FIELDS, SHORT_ID, STATUS_STATES, SYSTEM_HANDLE, type ScopeActor, type ScopeCap, type ScopeCaps, type ScopeChange, type ScopeDecision, type ScopeDirection, type ScopeEntry, type ScopeGroupIdentity, type ScopeGroupRules, type ScopeList, type ScopeNode, type ScopePolicyCap, type ScopePreset, type ScopeRules, type ScopeSubject, type ScopeView, type ScopeWorld, type SecretScanner, type Selector, type SendParams, type SendResult, type Sql, type SqlValue, type StatusState, type StopNudge, type StopWaitResult, TITLE_CHARS, type ThreadMessage, type Verdict, VerdictCache, WAKES, type Wake, type WhyResult, type XAllow, type XContact, XCounters, type XDelivered, type XEnvelopeFrom, XLIMITS, type XLimitConfig, type XLimits, type XListed, type XMessage, type XRelease, admits, allowlisted, applyRedactions, canonicalPolicy, cleanLabels, cleanText, clip, clipPasses, clipped, clock, collapseTools, createSecretScanner, crossFleet, dayStart, declaredLimits, deliveryText, digestText, dropQuoted, effectiveSelector, expandRules, findPii, findSecret, gateRefusal, groupCapKey, handbackOutput, harnessFits, hasScope, honestSender, isLeaving, joinRedacted, judgeBoundary, judgeDetailForAgents, judgeFellBack, judgeModel, judgePrompt, keepLast, keptHarness, leavesFleet, matches, mergeVerdicts, narrower, newId, newJudgeStats, one, parseJudgePolicy, parsePolicy, parseVerdict, parseXMessage, peekLevel, policyRefusal, prefilter, presenceText, presetOf, randomHex, reachLines, readText, receiptLines, redactPii, redactSecrets, redactionKind, relation, relationText, replyGrant, runJudge, sameSecret, scopeDecision, scopeLists, seqList, setImplementationPrefixes, sha256Hex, shortId, steerNote, stripControl, stripRelaySpans, stripSteerNote, unreachable, untilTomorrow, utf8Bytes, validHandle, validHarness, validSelector, wakeText, widenText, wrapMessage, wrapPeek, xlimits };
|
|
2559
|
+
export { ACCOUNT_HANDLE, ACTIVITY_KINDS, type ActivityKind, type ActivityRow, type Admission, type AdoptResult, type AskResult, type Attachment, type AuditRow, BROKER_CAPABILITIES, type BreakerAdmission, CAPABILITIES, CLI_NAME, type Claim, type CoreOptions, type CrossAccountGate, type CrossAccountLink, DEFAULT_IMPLEMENTATION_PREFIXES, DEFAULT_JUDGE_BUDGET, DEFAULT_JUDGE_EFFORT, type DataClass, ERROR_CODES, type EndpointKind, type EndpointRow, type EndpointView, type Envelope, type ErrorCode, FakeJudgeClient, GENERIC_HARNESSES, GONE_PREFIX, GROUP_NAME, type GateContext, type GateVerdict, HANDBACK_CUT, HUMAN_HANDLE, HttpJudgeClient, type HttpJudgeOptions, JUDGE_BREAKER_DEFAULTS, JUDGE_CATEGORIES, JUDGE_EFFORTS, JUDGE_FALLBACK_NOTE, JUDGE_MAX_OUTPUT_TOKENS, JUDGE_PROMPT_VERSION, JUDGE_PROVIDERS, JUDGE_VERDICTS, type JudgeAuditDetail, type JudgeAuditEvent, type JudgeBoundary, JudgeBreaker, type JudgeBreakerOptions, type JudgeBudget, type JudgeCategory, type JudgeClient, type JudgeContext, type JudgeEffort, type JudgeHeld, type JudgeModelConfig, type JudgePolicy, type JudgeProvider, JudgeProviderError, type JudgeRedacted, type JudgeSide, type JudgeStats, type JudgeVerdict, LIMITS, MESSAGE_KINDS, type MessageKind, type MessageReceipts, type MessageRow, OMITTED, PASS_GATE, PEEK, PREAMBLE, PREAMBLE_BRIEF, PRESET_LISTS, PROTOCOL, type PeekLevel, type PeekResult, type PeekSeen, type PeekState, type PeekTurn, type PeerView, type PiiHit, type Policy, type PolicyDefaults, type PolicyGroup, type PolicyRule, type PolicySubject, type Prefiltered, type Presence, REDACTION_KINDS, type Reach, type Redaction, RelayCore, RelayError, type RunJudgeInput, SCOPE_PRESETS, SERVER_FIELDS, SHORT_ID, STATUS_STATES, SYSTEM_HANDLE, type ScopeActor, type ScopeCap, type ScopeCaps, type ScopeChange, type ScopeDecision, type ScopeDirection, type ScopeEntry, type ScopeGroupIdentity, type ScopeGroupRules, type ScopeList, type ScopeNode, type ScopePolicyCap, type ScopePreset, type ScopeRules, type ScopeSubject, type ScopeView, type ScopeWorld, type SecretScanner, type Selector, type SendParams, type SendResult, type Sql, type SqlValue, type StatusState, type StopNudge, type StopWaitResult, TITLE_CHARS, type ThreadMessage, type Verdict, VerdictCache, WAKES, type Wake, type WhyResult, type XAllow, type XContact, XCounters, type XDelivered, type XEnvelopeFrom, XLIMITS, type XLimitConfig, type XLimits, type XListed, type XMessage, type XRelease, admits, allowlisted, applyRedactions, canonicalPolicy, cleanLabels, cleanText, clip, clipPasses, clipped, clock, collapseTools, createSecretScanner, crossFleet, dayStart, declaredLimits, deliveryText, digestText, dropQuoted, effectiveSelector, expandRules, findPii, findSecret, gateRefusal, groupCapKey, handbackOutput, harnessFits, hasScope, honestSender, isLeaving, joinRedacted, judgeBoundary, judgeDetailForAgents, judgeFellBack, judgeModel, judgePrompt, keepLast, keptHarness, leavesFleet, matches, mergeVerdicts, narrower, newId, newJudgeStats, one, parseJudgePolicy, parsePolicy, parseVerdict, parseXMessage, peekLevel, policyRefusal, prefilter, presenceText, presetOf, randomHex, reachLines, readText, receiptLines, redactPii, redactSecrets, redactionKind, relation, relationText, replyGrant, runJudge, sameSecret, scopeDecision, scopeLists, seqList, setImplementationPrefixes, sha256Hex, shortId, steerNote, stripControl, stripRelaySpans, stripSteerNote, unreachable, untilTomorrow, utf8Bytes, validHandle, validHarness, validSelector, wakeText, widenText, wrapMessage, wrapPeek, xlimits };
|
package/dist/index.js
CHANGED
|
@@ -179,7 +179,7 @@ var RelayError = class extends Error {
|
|
|
179
179
|
retryAfterS;
|
|
180
180
|
detail;
|
|
181
181
|
};
|
|
182
|
-
var WAKES = ["channels", "pi", "opencode-plugin", "codex-app-server", "none"];
|
|
182
|
+
var WAKES = ["channels", "pi", "opencode-plugin", "codex-app-server", "claude-inbox", "none"];
|
|
183
183
|
var ACTIVITY_KINDS = ["session_start", "tool", "stop", "note", "session_end"];
|
|
184
184
|
var MESSAGE_KINDS = ["message", "request", "reply", "status"];
|
|
185
185
|
var STATUS_STATES = ["working", "waiting", "blocked", "done"];
|
|
@@ -201,7 +201,7 @@ function keptHarness(held, claimed) {
|
|
|
201
201
|
return generic(claimed) && !generic(held) ? held : claimed;
|
|
202
202
|
}
|
|
203
203
|
var SCOPE_PRESETS = ["open", "tree", "parent", "none"];
|
|
204
|
-
var SERVER_FIELDS = ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation"];
|
|
204
|
+
var SERVER_FIELDS = ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation", "link"];
|
|
205
205
|
|
|
206
206
|
// src/scrub.ts
|
|
207
207
|
var KEY_KIND = "[A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----";
|
|
@@ -1327,6 +1327,7 @@ var RelayCore = class {
|
|
|
1327
1327
|
const messageColumns = this.sql.all(`PRAGMA table_info(messages)`).map((c) => c.name);
|
|
1328
1328
|
if (!messageColumns.includes("sender_tty")) this.sql.all(`ALTER TABLE messages ADD COLUMN sender_tty INTEGER`);
|
|
1329
1329
|
if (!messageColumns.includes("require_reply")) this.sql.all(`ALTER TABLE messages ADD COLUMN require_reply INTEGER`);
|
|
1330
|
+
if (!messageColumns.includes("link_parent")) this.sql.all(`ALTER TABLE messages ADD COLUMN link_parent TEXT`);
|
|
1330
1331
|
const endpointColumns = this.sql.all(`PRAGMA table_info(endpoints)`).map((c) => c.name);
|
|
1331
1332
|
if (!endpointColumns.includes("session")) this.sql.all(`ALTER TABLE endpoints ADD COLUMN session TEXT`);
|
|
1332
1333
|
if (!endpointColumns.includes("hook_seen")) this.sql.all(`ALTER TABLE endpoints ADD COLUMN hook_seen INTEGER`);
|
|
@@ -2278,30 +2279,52 @@ var RelayCore = class {
|
|
|
2278
2279
|
return this.whoami(row.id);
|
|
2279
2280
|
});
|
|
2280
2281
|
}
|
|
2281
|
-
/**
|
|
2282
|
-
* links a local root agent under `parent`, as a spawned worker sits: in the parent's tree one level down, under the policy's child scope.
|
|
2283
|
-
* Only the human at a terminal links another session, or an agent from its own shell with a terminal attached, under itself by default.
|
|
2284
|
-
* A session links itself with {@link joinSession}'s `under`
|
|
2285
|
-
*/
|
|
2286
2282
|
adopt(actor, params, options = {}) {
|
|
2287
|
-
|
|
2283
|
+
const prepared = this.scopeTx(() => {
|
|
2288
2284
|
const me = this.actorRow(actor);
|
|
2289
2285
|
if (actor.kind === "human") this.needTerminal(actor, me, params.endpoint);
|
|
2290
|
-
else if (
|
|
2291
|
-
throw this.scopeRefused(
|
|
2292
|
-
actor,
|
|
2293
|
-
me,
|
|
2294
|
-
{ target: String(params.endpoint), reason: "adopt" },
|
|
2295
|
-
`only the human at a terminal links another session under a parent; the session links itself with ${CLI_NAME} join --under`
|
|
2296
|
-
);
|
|
2286
|
+
else if (actor.auth === "lineage")
|
|
2287
|
+
throw this.scopeRefused(actor, me, { target: String(params.endpoint), reason: "adopt" }, "only the session itself may request a link");
|
|
2297
2288
|
const target = this.resolve(params.endpoint);
|
|
2298
2289
|
if (target.kind !== "agent" || !this.localRow(target)) throw new RelayError("invalid_params", "only a local agent endpoint can be linked");
|
|
2299
2290
|
const parentAddress = params.parent ?? (actor.kind === "human" ? void 0 : this.address(me));
|
|
2300
2291
|
if (parentAddress === void 0 || parentAddress === null || parentAddress === "")
|
|
2301
2292
|
throw new RelayError("invalid_params", "the human names the parent to link under");
|
|
2302
2293
|
const parent = this.resolve(parentAddress);
|
|
2303
|
-
|
|
2304
|
-
|
|
2294
|
+
if (actor.kind !== "human" && parent.id !== me.id)
|
|
2295
|
+
throw this.scopeRefused(actor, me, { target: this.address(target), reason: "adopt" }, "an agent may request a link only under itself");
|
|
2296
|
+
const result2 = { endpoint: this.whoami(target.id), parent: this.address(parent) };
|
|
2297
|
+
if (actor.kind === "human" || options.tty === true) {
|
|
2298
|
+
this.link(target, parent, this.actorDetail(actor, me));
|
|
2299
|
+
return { result: { ...result2, endpoint: this.whoami(target.id) } };
|
|
2300
|
+
}
|
|
2301
|
+
if (target.parent_ep !== null || target.parent_addr !== null) throw this.alreadyLinked(target);
|
|
2302
|
+
const auth2 = actor.auth === "self" ? "self" : "session";
|
|
2303
|
+
return { result: result2, sender: { from: me.id, auth: auth2 } };
|
|
2304
|
+
});
|
|
2305
|
+
if (!prepared.sender) return prepared.result;
|
|
2306
|
+
const { result } = prepared;
|
|
2307
|
+
const { from, auth } = prepared.sender;
|
|
2308
|
+
const send = {
|
|
2309
|
+
to: result.endpoint.address,
|
|
2310
|
+
kind: "request",
|
|
2311
|
+
ttl_s: 86400,
|
|
2312
|
+
body: `${result.parent} asks you to link under it as its worker. To accept, run ${CLI_NAME} link accept <id> with this message's id, or ${CLI_NAME} join --under ${result.parent} (relay_link_accept {id} or relay_join {under: "${result.parent}"}).`
|
|
2313
|
+
};
|
|
2314
|
+
const requested = (sent) => ({ ...result, ...sent.held ? { held: sent.held } : { requested: sent.id } });
|
|
2315
|
+
return options.send ? options.send(from, send, { link: auth }).then(requested) : requested(this.send(from, send, { link: auth }));
|
|
2316
|
+
}
|
|
2317
|
+
/** accepts a broker-issued request delivered to the caller's own session */
|
|
2318
|
+
linkAccept(actor, params) {
|
|
2319
|
+
return this.scopeTx(() => {
|
|
2320
|
+
if (actor.kind !== "endpoint" || actor.auth === "lineage") throw new RelayError("scope_refused", "only the session itself accepts a link request");
|
|
2321
|
+
const me = this.actorRow(actor);
|
|
2322
|
+
const request = this.delivered(me.id, params.id);
|
|
2323
|
+
if (!request?.link || request.kind !== "request") throw new RelayError("not_found", "no link request delivered to this endpoint");
|
|
2324
|
+
const parent = this.mustEndpoint(request.from.endpoint);
|
|
2325
|
+
if (parent.kind !== "agent") throw new RelayError("invalid_params", "the requesting parent is no longer an agent");
|
|
2326
|
+
this.link(me, parent, { ...this.actorDetail(actor, me), request: request.id });
|
|
2327
|
+
return { endpoint: this.whoami(me.id), parent: this.address(parent) };
|
|
2305
2328
|
});
|
|
2306
2329
|
}
|
|
2307
2330
|
/**
|
|
@@ -2368,7 +2391,7 @@ var RelayCore = class {
|
|
|
2368
2391
|
this.sql.all(`UPDATE endpoints SET parent_addr = ?, linked = ? WHERE id = ?`, key, this.now(), row.id);
|
|
2369
2392
|
this.scopeMemo?.ancestors.clear();
|
|
2370
2393
|
this.scopeMemo?.limited.clear();
|
|
2371
|
-
this.audit("adopt", row.id, null, { ...by, target: this.address(row), parent: this.address(parent) });
|
|
2394
|
+
this.audit("adopt", row.id, by.request ?? null, { ...by, target: this.address(row), parent: this.address(parent) });
|
|
2372
2395
|
return;
|
|
2373
2396
|
}
|
|
2374
2397
|
if (parent.kind !== "agent") throw new RelayError("invalid_params", "the parent is an agent endpoint");
|
|
@@ -2397,7 +2420,7 @@ var RelayCore = class {
|
|
|
2397
2420
|
);
|
|
2398
2421
|
this.scopeMemo?.ancestors.clear();
|
|
2399
2422
|
this.scopeMemo?.limited.clear();
|
|
2400
|
-
this.audit("adopt", row.id, null, { ...by, target: this.address(row), parent: this.address(parent) });
|
|
2423
|
+
this.audit("adopt", row.id, by.request ?? null, { ...by, target: this.address(row), parent: this.address(parent) });
|
|
2401
2424
|
}
|
|
2402
2425
|
/** a link of `row`, which already sits under a parent, to another one */
|
|
2403
2426
|
alreadyLinked(row) {
|
|
@@ -2504,6 +2527,7 @@ var RelayCore = class {
|
|
|
2504
2527
|
* sends without a terminal, so the scopes check it like any unplaced sender
|
|
2505
2528
|
*/
|
|
2506
2529
|
send(from, params, origin = {}) {
|
|
2530
|
+
if ("link" in params) throw new RelayError("invalid_params", "link is set by the relay, not the sender");
|
|
2507
2531
|
const sender = this.mustEndpoint(from);
|
|
2508
2532
|
const senderTty = sender.kind === "human" ? origin.tty === true ? 1 : 0 : null;
|
|
2509
2533
|
const unverified = sender.kind === "human" && origin.unverified === true;
|
|
@@ -2565,6 +2589,9 @@ var RelayCore = class {
|
|
|
2565
2589
|
const pending = [...unjoined.values()].map((grant) => ({ key: pendingKey(grant.token_hash), address: `${this.user}/${grant.name}` }));
|
|
2566
2590
|
const reached = recipients.length + pending.length;
|
|
2567
2591
|
if (reached > LIMITS.fanOutTotal) throw new RelayError("too_large", `the send reaches ${reached} endpoints, more than ${LIMITS.fanOutTotal}`);
|
|
2592
|
+
if (origin.link && (sender.kind !== "agent" || kind !== "request" || recipients.length !== 1 || pending.length || recipients[0].kind !== "agent" || !this.localRow(recipients[0])))
|
|
2593
|
+
throw new RelayError("invalid_params", "a link request is from an agent to one local root agent");
|
|
2594
|
+
if (origin.link && (recipients[0].parent_ep !== null || recipients[0].parent_addr !== null)) throw this.alreadyLinked(recipients[0]);
|
|
2568
2595
|
const requestedThread = this.threadRef(from, cleanText(params.thread, "thread", 64));
|
|
2569
2596
|
const payloadHash = this.hash(
|
|
2570
2597
|
JSON.stringify([
|
|
@@ -2577,7 +2604,8 @@ var RelayCore = class {
|
|
|
2577
2604
|
attachments.map((a) => a.path).sort(),
|
|
2578
2605
|
mime,
|
|
2579
2606
|
ttl,
|
|
2580
|
-
params.require_reply === true
|
|
2607
|
+
params.require_reply === true,
|
|
2608
|
+
...origin.link ? [origin.link] : []
|
|
2581
2609
|
])
|
|
2582
2610
|
);
|
|
2583
2611
|
if (clientId) {
|
|
@@ -2735,8 +2763,8 @@ var RelayCore = class {
|
|
|
2735
2763
|
const expires = now + ttl * 1e3;
|
|
2736
2764
|
const to = targets.length === 1 ? String(targets[0]) : targets.join(",");
|
|
2737
2765
|
this.sql.all(
|
|
2738
|
-
`INSERT INTO messages (id, thread, reply_to, kind, from_ep, to_addr, body, mime, data_class, attachments, hop, created_at, expires_at, sender_tty, require_reply)
|
|
2739
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2766
|
+
`INSERT INTO messages (id, thread, reply_to, kind, from_ep, to_addr, body, mime, data_class, attachments, hop, created_at, expires_at, sender_tty, require_reply, link_parent)
|
|
2767
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2740
2768
|
id,
|
|
2741
2769
|
thread,
|
|
2742
2770
|
parent?.id ?? null,
|
|
@@ -2751,7 +2779,8 @@ var RelayCore = class {
|
|
|
2751
2779
|
now,
|
|
2752
2780
|
expires,
|
|
2753
2781
|
senderTty,
|
|
2754
|
-
params.require_reply === true ? 1 : null
|
|
2782
|
+
params.require_reply === true ? 1 : null,
|
|
2783
|
+
origin.link ? this.address(sender) : null
|
|
2755
2784
|
);
|
|
2756
2785
|
if (parent) this.sql.all(`UPDATE deliveries SET acked_at = ? WHERE message_id = ? AND endpoint = ? AND acked_at IS NULL`, now, parent.id, from);
|
|
2757
2786
|
const deliveries = [];
|
|
@@ -2793,6 +2822,13 @@ var RelayCore = class {
|
|
|
2793
2822
|
);
|
|
2794
2823
|
}
|
|
2795
2824
|
this.audit("send", from, id, { to: deliveries.map((d) => d.address), kind, bytes, hop, thread, data_class: dataClass });
|
|
2825
|
+
if (origin.link)
|
|
2826
|
+
this.audit("adopt_request", from, id, {
|
|
2827
|
+
by: this.address(sender),
|
|
2828
|
+
auth: origin.link,
|
|
2829
|
+
target: this.address(recipients[0]),
|
|
2830
|
+
parent: this.address(sender)
|
|
2831
|
+
});
|
|
2796
2832
|
for (const recipient of recipients) this.detectPingPong(sender, recipient);
|
|
2797
2833
|
return sent;
|
|
2798
2834
|
});
|
|
@@ -5538,6 +5574,7 @@ ${stderr}` : "";
|
|
|
5538
5574
|
created_at: iso(row.created_at),
|
|
5539
5575
|
expires_at: iso(row.expires_at),
|
|
5540
5576
|
...row.require_reply === 1 ? { require_reply: true } : {},
|
|
5577
|
+
...row.link_parent ? { link: { parent: row.link_parent } } : {},
|
|
5541
5578
|
...kin
|
|
5542
5579
|
};
|
|
5543
5580
|
}
|
package/package.json
CHANGED
package/src/core.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CLI_NAME,
|
|
5
5
|
type ActivityKind,
|
|
6
6
|
type ActivityRow,
|
|
7
|
+
type AdoptResult,
|
|
7
8
|
type AuditRow,
|
|
8
9
|
type Claim,
|
|
9
10
|
type DataClass,
|
|
@@ -291,8 +292,11 @@ export interface MessageRow {
|
|
|
291
292
|
expires_at: number
|
|
292
293
|
sender_tty: number | null
|
|
293
294
|
require_reply: number | null
|
|
295
|
+
link_parent: string | null
|
|
294
296
|
}
|
|
295
297
|
|
|
298
|
+
type AdoptSend = (from: string, params: SendParams, origin: { link: "session" | "self" }) => Promise<SendResult>
|
|
299
|
+
|
|
296
300
|
export interface CoreOptions {
|
|
297
301
|
/** the local user's address part; `me` until a login names one */
|
|
298
302
|
user?: string
|
|
@@ -523,6 +527,7 @@ export class RelayCore {
|
|
|
523
527
|
const messageColumns = this.sql.all<{ name: string }>(`PRAGMA table_info(messages)`).map((c) => c.name)
|
|
524
528
|
if (!messageColumns.includes("sender_tty")) this.sql.all(`ALTER TABLE messages ADD COLUMN sender_tty INTEGER`)
|
|
525
529
|
if (!messageColumns.includes("require_reply")) this.sql.all(`ALTER TABLE messages ADD COLUMN require_reply INTEGER`)
|
|
530
|
+
if (!messageColumns.includes("link_parent")) this.sql.all(`ALTER TABLE messages ADD COLUMN link_parent TEXT`)
|
|
526
531
|
const endpointColumns = this.sql.all<{ name: string }>(`PRAGMA table_info(endpoints)`).map((c) => c.name)
|
|
527
532
|
if (!endpointColumns.includes("session")) this.sql.all(`ALTER TABLE endpoints ADD COLUMN session TEXT`)
|
|
528
533
|
if (!endpointColumns.includes("hook_seen")) this.sql.all(`ALTER TABLE endpoints ADD COLUMN hook_seen INTEGER`)
|
|
@@ -1592,30 +1597,60 @@ export class RelayCore {
|
|
|
1592
1597
|
})
|
|
1593
1598
|
}
|
|
1594
1599
|
|
|
1595
|
-
/**
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1600
|
+
/** the human links a local root directly; an agent asks it to consent through the normal send path */
|
|
1601
|
+
adopt(actor: ScopeActor, params: { endpoint: unknown; parent?: unknown }, options?: { tty?: boolean }): AdoptResult
|
|
1602
|
+
adopt(actor: ScopeActor, params: { endpoint: unknown; parent?: unknown }, options: { tty?: boolean; send: AdoptSend }): AdoptResult | Promise<AdoptResult>
|
|
1603
|
+
adopt(
|
|
1604
|
+
actor: ScopeActor,
|
|
1605
|
+
params: { endpoint: unknown; parent?: unknown },
|
|
1606
|
+
options: { tty?: boolean; send?: AdoptSend } = {},
|
|
1607
|
+
): AdoptResult | Promise<AdoptResult> {
|
|
1608
|
+
const prepared: { result: AdoptResult; sender?: { from: string; auth: "session" | "self" } } = this.scopeTx(() => {
|
|
1602
1609
|
const me = this.actorRow(actor)
|
|
1603
1610
|
if (actor.kind === "human") this.needTerminal(actor, me, params.endpoint)
|
|
1604
|
-
else if (
|
|
1605
|
-
throw this.scopeRefused(
|
|
1606
|
-
actor,
|
|
1607
|
-
me,
|
|
1608
|
-
{ target: String(params.endpoint), reason: "adopt" },
|
|
1609
|
-
`only the human at a terminal links another session under a parent; the session links itself with ${CLI_NAME} join --under`,
|
|
1610
|
-
)
|
|
1611
|
+
else if (actor.auth === "lineage")
|
|
1612
|
+
throw this.scopeRefused(actor, me, { target: String(params.endpoint), reason: "adopt" }, "only the session itself may request a link")
|
|
1611
1613
|
const target = this.resolve(params.endpoint)
|
|
1612
1614
|
if (target.kind !== "agent" || !this.localRow(target)) throw new RelayError("invalid_params", "only a local agent endpoint can be linked")
|
|
1613
1615
|
const parentAddress = params.parent ?? (actor.kind === "human" ? undefined : this.address(me))
|
|
1614
1616
|
if (parentAddress === undefined || parentAddress === null || parentAddress === "")
|
|
1615
1617
|
throw new RelayError("invalid_params", "the human names the parent to link under")
|
|
1616
1618
|
const parent = this.resolve(parentAddress)
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
+
if (actor.kind !== "human" && parent.id !== me.id)
|
|
1620
|
+
throw this.scopeRefused(actor, me, { target: this.address(target), reason: "adopt" }, "an agent may request a link only under itself")
|
|
1621
|
+
const result: AdoptResult = { endpoint: this.whoami(target.id), parent: this.address(parent) }
|
|
1622
|
+
if (actor.kind === "human" || options.tty === true) {
|
|
1623
|
+
this.link(target, parent, this.actorDetail(actor, me))
|
|
1624
|
+
return { result: { ...result, endpoint: this.whoami(target.id) } }
|
|
1625
|
+
}
|
|
1626
|
+
if (target.parent_ep !== null || target.parent_addr !== null) throw this.alreadyLinked(target)
|
|
1627
|
+
const auth = actor.auth === "self" ? "self" : "session"
|
|
1628
|
+
return { result, sender: { from: me.id, auth } }
|
|
1629
|
+
})
|
|
1630
|
+
if (!prepared.sender) return prepared.result
|
|
1631
|
+
const { result } = prepared
|
|
1632
|
+
const { from, auth } = prepared.sender
|
|
1633
|
+
const send: SendParams = {
|
|
1634
|
+
to: result.endpoint.address,
|
|
1635
|
+
kind: "request",
|
|
1636
|
+
ttl_s: 86_400,
|
|
1637
|
+
body: `${result.parent} asks you to link under it as its worker. To accept, run ${CLI_NAME} link accept <id> with this message's id, or ${CLI_NAME} join --under ${result.parent} (relay_link_accept {id} or relay_join {under: "${result.parent}"}).`,
|
|
1638
|
+
}
|
|
1639
|
+
const requested = (sent: SendResult): AdoptResult => ({ ...result, ...(sent.held ? { held: sent.held } : { requested: sent.id }) })
|
|
1640
|
+
return options.send ? options.send(from, send, { link: auth }).then(requested) : requested(this.send(from, send, { link: auth }))
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
/** accepts a broker-issued request delivered to the caller's own session */
|
|
1644
|
+
linkAccept(actor: ScopeActor, params: { id: unknown }): { endpoint: EndpointView; parent: string } {
|
|
1645
|
+
return this.scopeTx(() => {
|
|
1646
|
+
if (actor.kind !== "endpoint" || actor.auth === "lineage") throw new RelayError("scope_refused", "only the session itself accepts a link request")
|
|
1647
|
+
const me = this.actorRow(actor)
|
|
1648
|
+
const request = this.delivered(me.id, params.id)
|
|
1649
|
+
if (!request?.link || request.kind !== "request") throw new RelayError("not_found", "no link request delivered to this endpoint")
|
|
1650
|
+
const parent = this.mustEndpoint(request.from.endpoint)
|
|
1651
|
+
if (parent.kind !== "agent") throw new RelayError("invalid_params", "the requesting parent is no longer an agent")
|
|
1652
|
+
this.link(me, parent, { ...this.actorDetail(actor, me), request: request.id })
|
|
1653
|
+
return { endpoint: this.whoami(me.id), parent: this.address(parent) }
|
|
1619
1654
|
})
|
|
1620
1655
|
}
|
|
1621
1656
|
|
|
@@ -1676,7 +1711,7 @@ export class RelayCore {
|
|
|
1676
1711
|
* `row`, a root, and its subtree moved under `parent`, a local agent that is neither `row` nor one of its descendants; a parent on another
|
|
1677
1712
|
* machine goes to {@link linkAcross}
|
|
1678
1713
|
*/
|
|
1679
|
-
private link(row: EndpointRow, parent: EndpointRow, by: { by: string; auth: string }): void {
|
|
1714
|
+
private link(row: EndpointRow, parent: EndpointRow, by: { by: string; auth: string; request?: string }): void {
|
|
1680
1715
|
if (!this.localRow(parent)) {
|
|
1681
1716
|
const key = this.linkAcross(row, parent)
|
|
1682
1717
|
if (row.parent_addr === key) return
|
|
@@ -1684,7 +1719,7 @@ export class RelayCore {
|
|
|
1684
1719
|
this.sql.all(`UPDATE endpoints SET parent_addr = ?, linked = ? WHERE id = ?`, key, this.now(), row.id)
|
|
1685
1720
|
this.scopeMemo?.ancestors.clear()
|
|
1686
1721
|
this.scopeMemo?.limited.clear()
|
|
1687
|
-
this.audit("adopt", row.id, null, { ...by, target: this.address(row), parent: this.address(parent) })
|
|
1722
|
+
this.audit("adopt", row.id, by.request ?? null, { ...by, target: this.address(row), parent: this.address(parent) })
|
|
1688
1723
|
return
|
|
1689
1724
|
}
|
|
1690
1725
|
if (parent.kind !== "agent") throw new RelayError("invalid_params", "the parent is an agent endpoint")
|
|
@@ -1713,7 +1748,7 @@ export class RelayCore {
|
|
|
1713
1748
|
)
|
|
1714
1749
|
this.scopeMemo?.ancestors.clear()
|
|
1715
1750
|
this.scopeMemo?.limited.clear()
|
|
1716
|
-
this.audit("adopt", row.id, null, { ...by, target: this.address(row), parent: this.address(parent) })
|
|
1751
|
+
this.audit("adopt", row.id, by.request ?? null, { ...by, target: this.address(row), parent: this.address(parent) })
|
|
1717
1752
|
}
|
|
1718
1753
|
|
|
1719
1754
|
/** a link of `row`, which already sits under a parent, to another one */
|
|
@@ -1829,7 +1864,8 @@ export class RelayCore {
|
|
|
1829
1864
|
* `origin.tty` is recorded on messages the human inbox sends: whether its client said a terminal was attached. `origin.unverified`: the human
|
|
1830
1865
|
* sends without a terminal, so the scopes check it like any unplaced sender
|
|
1831
1866
|
*/
|
|
1832
|
-
send(from: string, params: SendParams, origin: { tty?: boolean; unverified?: boolean } = {}): SendResult {
|
|
1867
|
+
send(from: string, params: SendParams, origin: { tty?: boolean; unverified?: boolean; link?: "session" | "self" } = {}): SendResult {
|
|
1868
|
+
if ("link" in params) throw new RelayError("invalid_params", "link is set by the relay, not the sender")
|
|
1833
1869
|
const sender = this.mustEndpoint(from)
|
|
1834
1870
|
const senderTty = sender.kind === "human" ? (origin.tty === true ? 1 : 0) : null
|
|
1835
1871
|
const unverified = sender.kind === "human" && origin.unverified === true
|
|
@@ -1897,6 +1933,17 @@ export class RelayCore {
|
|
|
1897
1933
|
const pending = [...unjoined.values()].map((grant) => ({ key: pendingKey(grant.token_hash), address: `${this.user}/${grant.name}` }))
|
|
1898
1934
|
const reached = recipients.length + pending.length
|
|
1899
1935
|
if (reached > LIMITS.fanOutTotal) throw new RelayError("too_large", `the send reaches ${reached} endpoints, more than ${LIMITS.fanOutTotal}`)
|
|
1936
|
+
if (
|
|
1937
|
+
origin.link &&
|
|
1938
|
+
(sender.kind !== "agent" ||
|
|
1939
|
+
kind !== "request" ||
|
|
1940
|
+
recipients.length !== 1 ||
|
|
1941
|
+
pending.length ||
|
|
1942
|
+
recipients[0]!.kind !== "agent" ||
|
|
1943
|
+
!this.localRow(recipients[0]!))
|
|
1944
|
+
)
|
|
1945
|
+
throw new RelayError("invalid_params", "a link request is from an agent to one local root agent")
|
|
1946
|
+
if (origin.link && (recipients[0]!.parent_ep !== null || recipients[0]!.parent_addr !== null)) throw this.alreadyLinked(recipients[0]!)
|
|
1900
1947
|
const requestedThread = this.threadRef(from, cleanText(params.thread, "thread", 64))
|
|
1901
1948
|
const payloadHash = this.hash(
|
|
1902
1949
|
JSON.stringify([
|
|
@@ -1910,6 +1957,7 @@ export class RelayCore {
|
|
|
1910
1957
|
mime,
|
|
1911
1958
|
ttl,
|
|
1912
1959
|
params.require_reply === true,
|
|
1960
|
+
...(origin.link ? [origin.link] : []),
|
|
1913
1961
|
]),
|
|
1914
1962
|
)
|
|
1915
1963
|
if (clientId) {
|
|
@@ -2079,8 +2127,8 @@ export class RelayCore {
|
|
|
2079
2127
|
const expires = now + ttl * 1000
|
|
2080
2128
|
const to = targets.length === 1 ? String(targets[0]) : targets.join(",")
|
|
2081
2129
|
this.sql.all(
|
|
2082
|
-
`INSERT INTO messages (id, thread, reply_to, kind, from_ep, to_addr, body, mime, data_class, attachments, hop, created_at, expires_at, sender_tty, require_reply)
|
|
2083
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2130
|
+
`INSERT INTO messages (id, thread, reply_to, kind, from_ep, to_addr, body, mime, data_class, attachments, hop, created_at, expires_at, sender_tty, require_reply, link_parent)
|
|
2131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
2084
2132
|
id,
|
|
2085
2133
|
thread,
|
|
2086
2134
|
parent?.id ?? null,
|
|
@@ -2096,6 +2144,7 @@ export class RelayCore {
|
|
|
2096
2144
|
expires,
|
|
2097
2145
|
senderTty,
|
|
2098
2146
|
params.require_reply === true ? 1 : null,
|
|
2147
|
+
origin.link ? this.address(sender) : null,
|
|
2099
2148
|
)
|
|
2100
2149
|
if (parent) this.sql.all(`UPDATE deliveries SET acked_at = ? WHERE message_id = ? AND endpoint = ? AND acked_at IS NULL`, now, parent.id, from)
|
|
2101
2150
|
const deliveries: SendResult["deliveries"] = []
|
|
@@ -2137,6 +2186,13 @@ export class RelayCore {
|
|
|
2137
2186
|
)
|
|
2138
2187
|
}
|
|
2139
2188
|
this.audit("send", from, id, { to: deliveries.map((d) => d.address), kind, bytes, hop, thread, data_class: dataClass })
|
|
2189
|
+
if (origin.link)
|
|
2190
|
+
this.audit("adopt_request", from, id, {
|
|
2191
|
+
by: this.address(sender),
|
|
2192
|
+
auth: origin.link,
|
|
2193
|
+
target: this.address(recipients[0]!),
|
|
2194
|
+
parent: this.address(sender),
|
|
2195
|
+
})
|
|
2140
2196
|
for (const recipient of recipients) this.detectPingPong(sender, recipient)
|
|
2141
2197
|
return sent
|
|
2142
2198
|
})
|
|
@@ -5249,6 +5305,7 @@ export class RelayCore {
|
|
|
5249
5305
|
created_at: iso(row.created_at)!,
|
|
5250
5306
|
expires_at: iso(row.expires_at)!,
|
|
5251
5307
|
...(row.require_reply === 1 ? { require_reply: true as const } : {}),
|
|
5308
|
+
...(row.link_parent ? { link: { parent: row.link_parent } } : {}),
|
|
5252
5309
|
...kin,
|
|
5253
5310
|
}
|
|
5254
5311
|
}
|
package/src/protocol.ts
CHANGED
|
@@ -112,8 +112,8 @@ export type DataClass = "public" | "private"
|
|
|
112
112
|
export type Presence = "online" | "idle" | "offline"
|
|
113
113
|
export type StatusState = "working" | "waiting" | "blocked" | "done"
|
|
114
114
|
/** how an idle agent is woken by a message: a channel push, the pi extension, the OpenCode plugin's waiter or codex app-server; `none` reads at its next turn */
|
|
115
|
-
export type Wake = "channels" | "pi" | "opencode-plugin" | "codex-app-server" | "none"
|
|
116
|
-
export const WAKES: readonly Wake[] = ["channels", "pi", "opencode-plugin", "codex-app-server", "none"]
|
|
115
|
+
export type Wake = "channels" | "pi" | "opencode-plugin" | "codex-app-server" | "claude-inbox" | "none"
|
|
116
|
+
export const WAKES: readonly Wake[] = ["channels", "pi", "opencode-plugin", "codex-app-server", "claude-inbox", "none"]
|
|
117
117
|
|
|
118
118
|
export type ActivityKind = "session_start" | "tool" | "stop" | "note" | "session_end"
|
|
119
119
|
export const ACTIVITY_KINDS: readonly ActivityKind[] = ["session_start", "tool", "stop", "note", "session_end"]
|
|
@@ -358,6 +358,8 @@ export interface Envelope {
|
|
|
358
358
|
expires_at: string
|
|
359
359
|
/** a request whose sender holds the recipient's stop until it replies */
|
|
360
360
|
require_reply?: true
|
|
361
|
+
/** a broker-issued request to link under its sender */
|
|
362
|
+
link?: { parent: string }
|
|
361
363
|
/** set by inbox on a message a background hook already showed (and marked read), whose text may reach the agent only after this call */
|
|
362
364
|
shown_at?: string
|
|
363
365
|
/**
|
|
@@ -473,6 +475,13 @@ export interface SendResult {
|
|
|
473
475
|
redacted?: JudgeRedacted
|
|
474
476
|
}
|
|
475
477
|
|
|
478
|
+
export interface AdoptResult {
|
|
479
|
+
endpoint: EndpointView
|
|
480
|
+
parent: string
|
|
481
|
+
requested?: string
|
|
482
|
+
held?: JudgeHeld
|
|
483
|
+
}
|
|
484
|
+
|
|
476
485
|
/**
|
|
477
486
|
* a message the caller sent and, per recipient, when it read the message (acked), or when a background hook delivered it (acked by that
|
|
478
487
|
* showing; the agent gets it with its next request), and when a hook or channel showed it: ISO times, or null
|
|
@@ -493,4 +502,4 @@ export interface AuditRow {
|
|
|
493
502
|
}
|
|
494
503
|
|
|
495
504
|
/** fields only the server sets: a client that sends them gets invalid_params */
|
|
496
|
-
export const SERVER_FIELDS = ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation"] as const
|
|
505
|
+
export const SERVER_FIELDS = ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation", "link"] as const
|