@scp-dev/core 0.1.4 → 0.1.5
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 +8 -2
- package/dist/index.js +20 -8
- package/package.json +1 -1
- package/src/core.ts +12 -7
- package/src/protocol.ts +16 -1
package/dist/index.d.ts
CHANGED
|
@@ -346,7 +346,7 @@ declare const LIMITS: {
|
|
|
346
346
|
readonly hookEndedGraceS: 30;
|
|
347
347
|
readonly restartGraceS: 60;
|
|
348
348
|
readonly injectMessages: 5;
|
|
349
|
-
readonly injectBytes:
|
|
349
|
+
readonly injectBytes: 7168;
|
|
350
350
|
readonly pushBytes: 20480;
|
|
351
351
|
/** the stops a session's push may continue between two prompts */
|
|
352
352
|
readonly pushes: 20;
|
|
@@ -422,6 +422,12 @@ declare const MESSAGE_KINDS: readonly MessageKind[];
|
|
|
422
422
|
declare const STATUS_STATES: readonly StatusState[];
|
|
423
423
|
declare function validHandle(handle: string): boolean;
|
|
424
424
|
declare function validHarness(harness: string): boolean;
|
|
425
|
+
/** harness names of the path a claim came by (a bare CLI, hook or MCP shim), not of the agent behind it */
|
|
426
|
+
declare const GENERIC_HARNESSES: ReadonlySet<string>;
|
|
427
|
+
/** whether a claim of harness `claimed` may take a row of harness `held`: the same harness, or a generic row, which heals to the claim's */
|
|
428
|
+
declare function harnessFits(held: string | null, claimed: string): boolean;
|
|
429
|
+
/** the harness a claimed row keeps: the claim's, except that a generic claim never replaces a known harness */
|
|
430
|
+
declare function keptHarness(held: string | null, claimed: string): string;
|
|
425
431
|
interface Claim {
|
|
426
432
|
handle?: string;
|
|
427
433
|
harness?: string;
|
|
@@ -2492,4 +2498,4 @@ declare function sha256Hex(text: string): string;
|
|
|
2492
2498
|
/** the UTF-8 byte length of text, the unit every byte limit counts in */
|
|
2493
2499
|
declare function utf8Bytes(text: string): number;
|
|
2494
2500
|
|
|
2495
|
-
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, 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, hasScope, honestSender, isLeaving, joinRedacted, judgeBoundary, judgeDetailForAgents, judgeFellBack, judgeModel, judgePrompt, keepLast, 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 };
|
|
2501
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -126,7 +126,7 @@ var LIMITS = {
|
|
|
126
126
|
hookEndedGraceS: 30,
|
|
127
127
|
restartGraceS: 60,
|
|
128
128
|
injectMessages: 5,
|
|
129
|
-
injectBytes:
|
|
129
|
+
injectBytes: 7168,
|
|
130
130
|
pushBytes: 20480,
|
|
131
131
|
/** the stops a session's push may continue between two prompts */
|
|
132
132
|
pushes: 20,
|
|
@@ -192,6 +192,14 @@ function validHandle(handle) {
|
|
|
192
192
|
function validHarness(harness) {
|
|
193
193
|
return HARNESS.test(harness);
|
|
194
194
|
}
|
|
195
|
+
var GENERIC_HARNESSES = /* @__PURE__ */ new Set(["cli", "hook", "mcp", "shell"]);
|
|
196
|
+
var generic = (harness) => harness === null || GENERIC_HARNESSES.has(harness);
|
|
197
|
+
function harnessFits(held, claimed) {
|
|
198
|
+
return held === claimed || generic(held);
|
|
199
|
+
}
|
|
200
|
+
function keptHarness(held, claimed) {
|
|
201
|
+
return generic(claimed) && !generic(held) ? held : claimed;
|
|
202
|
+
}
|
|
195
203
|
var SCOPE_PRESETS = ["open", "tree", "parent", "none"];
|
|
196
204
|
var SERVER_FIELDS = ["from", "id", "seq", "hop", "created_at", "auth", "expires_at", "relation"];
|
|
197
205
|
|
|
@@ -1086,6 +1094,7 @@ function utf8Bytes(text) {
|
|
|
1086
1094
|
}
|
|
1087
1095
|
|
|
1088
1096
|
// src/core.ts
|
|
1097
|
+
var GENERIC_SQL = `harness IN (${[...GENERIC_HARNESSES].map((h) => `'${h}'`).join(", ")})`;
|
|
1089
1098
|
var HUMAN_HANDLE = "_human";
|
|
1090
1099
|
var TITLE_CHARS = 64;
|
|
1091
1100
|
var SYSTEM_HANDLE = "_relay";
|
|
@@ -1595,7 +1604,7 @@ var RelayCore = class {
|
|
|
1595
1604
|
const bare = transient && fields.session !== null && BARE_PID_KEY.test(fields.session);
|
|
1596
1605
|
let row = fields.session ? one(
|
|
1597
1606
|
this.sql,
|
|
1598
|
-
`SELECT * FROM endpoints WHERE kind = 'agent' AND harness = ?
|
|
1607
|
+
`SELECT * FROM endpoints WHERE kind = 'agent' AND (harness = ? OR ${GENERIC_SQL})
|
|
1599
1608
|
AND (session = ? OR (? = 1 AND substr(session, 1, ?) = ? AND (connections = 0 OR hook_seen >= ?)))
|
|
1600
1609
|
AND substr(handle, 1, ${GONE_PREFIX.length}) != '${GONE_PREFIX}' ORDER BY COALESCE(last_seen, created_at) DESC LIMIT 1`,
|
|
1601
1610
|
harness,
|
|
@@ -1624,7 +1633,7 @@ var RelayCore = class {
|
|
|
1624
1633
|
if (!held) {
|
|
1625
1634
|
this.sql.all(`UPDATE endpoints SET handle = ? WHERE id = ?`, handle, hooks.id);
|
|
1626
1635
|
settled = true;
|
|
1627
|
-
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && held.harness
|
|
1636
|
+
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && harnessFits(held.harness, harness)) {
|
|
1628
1637
|
if (this.restarts(held, this.placement(scope, now).parent, fields.process) || this.redeems(held, scope, { harness, ...fields })) {
|
|
1629
1638
|
;
|
|
1630
1639
|
({ moved, stopBlocks } = this.leaveHooks(hooks, held.id));
|
|
@@ -1654,12 +1663,12 @@ var RelayCore = class {
|
|
|
1654
1663
|
this.sql.all(`INSERT INTO endpoints (id, handle, kind, harness, labels, created_at) VALUES (?, ?, 'agent', ?, '{}', ?)`, id, handle, harness, now);
|
|
1655
1664
|
row = this.byId(id);
|
|
1656
1665
|
created = true;
|
|
1657
|
-
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && held.harness
|
|
1666
|
+
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && harnessFits(held.harness, harness) && held.id !== scope.subagent?.root && !(scope.subagent && held.session?.startsWith("sub:") && held.session !== fields.session)) {
|
|
1658
1667
|
own = transient && held.process !== null && fields.process !== null && sameHarness(held.process, fields.process);
|
|
1659
1668
|
const ended = held.hook_ended !== null || (held.last_seen ?? held.created_at) < now - LIMITS.restartGraceS * 1e3;
|
|
1660
1669
|
if ((!transient || own || ended) && (this.restarts(held, this.placement(scope, now).parent, fields.process) || this.redeems(held, scope, { harness, ...fields })))
|
|
1661
1670
|
row = held;
|
|
1662
|
-
} else if (scope.exact && held.kind === "agent" && held.process === null && held.session === null && held.harness
|
|
1671
|
+
} else if (scope.exact && held.kind === "agent" && held.process === null && held.session === null && harnessFits(held.harness, harness) && this.restarts(held, this.placement(scope, now).parent, null)) {
|
|
1663
1672
|
row = held;
|
|
1664
1673
|
joined = true;
|
|
1665
1674
|
live = presenceOf(held, now) !== "offline";
|
|
@@ -1684,7 +1693,7 @@ var RelayCore = class {
|
|
|
1684
1693
|
stop_blocks = CASE WHEN ? IS NOT NULL THEN ? WHEN ? IS NOT NULL AND session IS NOT ? THEN 0 ELSE stop_blocks END,
|
|
1685
1694
|
briefed = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE briefed END, introduced = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE introduced END,
|
|
1686
1695
|
directive_holds = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE directive_holds END WHERE id = ?`,
|
|
1687
|
-
harness,
|
|
1696
|
+
keptHarness(row.harness, harness),
|
|
1688
1697
|
fields.model,
|
|
1689
1698
|
fields.cwd,
|
|
1690
1699
|
fields.parent,
|
|
@@ -1813,7 +1822,7 @@ var RelayCore = class {
|
|
|
1813
1822
|
if ((held.session !== null || held.process !== null) && presenceOf(held, now) !== "offline") {
|
|
1814
1823
|
return new RelayError("unauthorized", `${handle} is held by a live ${held.harness} session: act as it from that session's own shell, no --as needed`);
|
|
1815
1824
|
}
|
|
1816
|
-
if (held.harness
|
|
1825
|
+
if (!harnessFits(held.harness, harness)) return new RelayError("unauthorized", `${handle} is a ${held.harness} endpoint: pass --harness ${held.harness}`);
|
|
1817
1826
|
return new RelayError("unauthorized", `${handle} is placed in another tree or under another parent than this shell`);
|
|
1818
1827
|
}
|
|
1819
1828
|
/**
|
|
@@ -1875,7 +1884,7 @@ var RelayCore = class {
|
|
|
1875
1884
|
const spent = this.spentGrant(scope);
|
|
1876
1885
|
if (spent?.used_by !== row.id) return false;
|
|
1877
1886
|
const lineage = this.lineageParent(scope.lineage);
|
|
1878
|
-
if (!lineage) return row.harness
|
|
1887
|
+
if (!lineage) return harnessFits(row.harness, claimant.harness) && spent.spent_cwd !== null && spent.spent_cwd === claimant.cwd;
|
|
1879
1888
|
return this.restarts(row, lineage, claimant.process) || this.ancestorsOf(row).includes(lineage.id);
|
|
1880
1889
|
}
|
|
1881
1890
|
/** the claim's grant, when an endpoint spent it */
|
|
@@ -6387,6 +6396,7 @@ export {
|
|
|
6387
6396
|
DEFAULT_JUDGE_EFFORT,
|
|
6388
6397
|
ERROR_CODES,
|
|
6389
6398
|
FakeJudgeClient,
|
|
6399
|
+
GENERIC_HARNESSES,
|
|
6390
6400
|
GONE_PREFIX,
|
|
6391
6401
|
GROUP_NAME,
|
|
6392
6402
|
HANDBACK_CUT,
|
|
@@ -6449,6 +6459,7 @@ export {
|
|
|
6449
6459
|
gateRefusal,
|
|
6450
6460
|
groupCapKey,
|
|
6451
6461
|
handbackOutput,
|
|
6462
|
+
harnessFits,
|
|
6452
6463
|
hasScope,
|
|
6453
6464
|
honestSender,
|
|
6454
6465
|
isLeaving,
|
|
@@ -6459,6 +6470,7 @@ export {
|
|
|
6459
6470
|
judgeModel,
|
|
6460
6471
|
judgePrompt,
|
|
6461
6472
|
keepLast,
|
|
6473
|
+
keptHarness,
|
|
6462
6474
|
leavesFleet,
|
|
6463
6475
|
matches,
|
|
6464
6476
|
mergeVerdicts,
|
package/package.json
CHANGED
package/src/core.ts
CHANGED
|
@@ -38,6 +38,9 @@ import {
|
|
|
38
38
|
type StopNudge,
|
|
39
39
|
type StopWaitResult,
|
|
40
40
|
type ThreadMessage,
|
|
41
|
+
GENERIC_HARNESSES,
|
|
42
|
+
harnessFits,
|
|
43
|
+
keptHarness,
|
|
41
44
|
validHandle,
|
|
42
45
|
validHarness,
|
|
43
46
|
type Wake,
|
|
@@ -70,6 +73,8 @@ import { findSecret, redactSecrets } from "./scrub"
|
|
|
70
73
|
import { one, type Sql, type SqlValue } from "./sql"
|
|
71
74
|
import { utf8Bytes } from "./utf8"
|
|
72
75
|
|
|
76
|
+
const GENERIC_SQL = `harness IN (${[...GENERIC_HARNESSES].map((h) => `'${h}'`).join(", ")})`
|
|
77
|
+
|
|
73
78
|
export const HUMAN_HANDLE = "_human"
|
|
74
79
|
/** an agent title's length cap */
|
|
75
80
|
export const TITLE_CHARS = 64
|
|
@@ -811,7 +816,7 @@ export class RelayCore {
|
|
|
811
816
|
let row = fields.session
|
|
812
817
|
? one<EndpointRow>(
|
|
813
818
|
this.sql,
|
|
814
|
-
`SELECT * FROM endpoints WHERE kind = 'agent' AND harness = ?
|
|
819
|
+
`SELECT * FROM endpoints WHERE kind = 'agent' AND (harness = ? OR ${GENERIC_SQL})
|
|
815
820
|
AND (session = ? OR (? = 1 AND substr(session, 1, ?) = ? AND (connections = 0 OR hook_seen >= ?)))
|
|
816
821
|
AND substr(handle, 1, ${GONE_PREFIX.length}) != '${GONE_PREFIX}' ORDER BY COALESCE(last_seen, created_at) DESC LIMIT 1`,
|
|
817
822
|
harness,
|
|
@@ -848,7 +853,7 @@ export class RelayCore {
|
|
|
848
853
|
if (!held) {
|
|
849
854
|
this.sql.all(`UPDATE endpoints SET handle = ? WHERE id = ?`, handle, hooks.id)
|
|
850
855
|
settled = true
|
|
851
|
-
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && held.harness
|
|
856
|
+
} else if (held.kind === "agent" && held.connections === 0 && presenceOf(held, now) === "offline" && harnessFits(held.harness, harness)) {
|
|
852
857
|
if (this.restarts(held, this.placement(scope, now).parent, fields.process) || this.redeems(held, scope, { harness, ...fields })) {
|
|
853
858
|
;({ moved, stopBlocks } = this.leaveHooks(hooks, held.id))
|
|
854
859
|
row = undefined
|
|
@@ -881,7 +886,7 @@ export class RelayCore {
|
|
|
881
886
|
held.kind === "agent" &&
|
|
882
887
|
held.connections === 0 &&
|
|
883
888
|
presenceOf(held, now) === "offline" &&
|
|
884
|
-
held.harness
|
|
889
|
+
harnessFits(held.harness, harness) &&
|
|
885
890
|
held.id !== scope.subagent?.root &&
|
|
886
891
|
!(scope.subagent && held.session?.startsWith("sub:") && held.session !== fields.session)
|
|
887
892
|
) {
|
|
@@ -897,7 +902,7 @@ export class RelayCore {
|
|
|
897
902
|
held.kind === "agent" &&
|
|
898
903
|
held.process === null &&
|
|
899
904
|
held.session === null &&
|
|
900
|
-
held.harness
|
|
905
|
+
harnessFits(held.harness, harness) &&
|
|
901
906
|
this.restarts(held, this.placement(scope, now).parent, null)
|
|
902
907
|
) {
|
|
903
908
|
row = held
|
|
@@ -924,7 +929,7 @@ export class RelayCore {
|
|
|
924
929
|
stop_blocks = CASE WHEN ? IS NOT NULL THEN ? WHEN ? IS NOT NULL AND session IS NOT ? THEN 0 ELSE stop_blocks END,
|
|
925
930
|
briefed = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE briefed END, introduced = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE introduced END,
|
|
926
931
|
directive_holds = CASE WHEN ? IS NOT NULL AND session IS NOT ? THEN NULL ELSE directive_holds END WHERE id = ?`,
|
|
927
|
-
harness,
|
|
932
|
+
keptHarness(row.harness, harness),
|
|
928
933
|
fields.model,
|
|
929
934
|
fields.cwd,
|
|
930
935
|
fields.parent,
|
|
@@ -1062,7 +1067,7 @@ export class RelayCore {
|
|
|
1062
1067
|
if ((held.session !== null || held.process !== null) && presenceOf(held, now) !== "offline") {
|
|
1063
1068
|
return new RelayError("unauthorized", `${handle} is held by a live ${held.harness} session: act as it from that session's own shell, no --as needed`)
|
|
1064
1069
|
}
|
|
1065
|
-
if (held.harness
|
|
1070
|
+
if (!harnessFits(held.harness, harness)) return new RelayError("unauthorized", `${handle} is a ${held.harness} endpoint: pass --harness ${held.harness}`)
|
|
1066
1071
|
return new RelayError("unauthorized", `${handle} is placed in another tree or under another parent than this shell`)
|
|
1067
1072
|
}
|
|
1068
1073
|
|
|
@@ -1132,7 +1137,7 @@ export class RelayCore {
|
|
|
1132
1137
|
const spent = this.spentGrant(scope)
|
|
1133
1138
|
if (spent?.used_by !== row.id) return false
|
|
1134
1139
|
const lineage = this.lineageParent(scope.lineage)
|
|
1135
|
-
if (!lineage) return row.harness
|
|
1140
|
+
if (!lineage) return harnessFits(row.harness, claimant.harness) && spent.spent_cwd !== null && spent.spent_cwd === claimant.cwd
|
|
1136
1141
|
return this.restarts(row, lineage, claimant.process) || this.ancestorsOf(row).includes(lineage.id)
|
|
1137
1142
|
}
|
|
1138
1143
|
|
package/src/protocol.ts
CHANGED
|
@@ -34,7 +34,7 @@ export const LIMITS = {
|
|
|
34
34
|
hookEndedGraceS: 30,
|
|
35
35
|
restartGraceS: 60,
|
|
36
36
|
injectMessages: 5,
|
|
37
|
-
injectBytes:
|
|
37
|
+
injectBytes: 7168,
|
|
38
38
|
pushBytes: 20480,
|
|
39
39
|
/** the stops a session's push may continue between two prompts */
|
|
40
40
|
pushes: 20,
|
|
@@ -144,6 +144,21 @@ export function validHarness(harness: string): boolean {
|
|
|
144
144
|
return HARNESS.test(harness)
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
/** harness names of the path a claim came by (a bare CLI, hook or MCP shim), not of the agent behind it */
|
|
148
|
+
export const GENERIC_HARNESSES: ReadonlySet<string> = new Set(["cli", "hook", "mcp", "shell"])
|
|
149
|
+
|
|
150
|
+
const generic = (harness: string | null): boolean => harness === null || GENERIC_HARNESSES.has(harness)
|
|
151
|
+
|
|
152
|
+
/** whether a claim of harness `claimed` may take a row of harness `held`: the same harness, or a generic row, which heals to the claim's */
|
|
153
|
+
export function harnessFits(held: string | null, claimed: string): boolean {
|
|
154
|
+
return held === claimed || generic(held)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** the harness a claimed row keeps: the claim's, except that a generic claim never replaces a known harness */
|
|
158
|
+
export function keptHarness(held: string | null, claimed: string): string {
|
|
159
|
+
return generic(claimed) && !generic(held) ? held! : claimed
|
|
160
|
+
}
|
|
161
|
+
|
|
147
162
|
export interface Claim {
|
|
148
163
|
handle?: string
|
|
149
164
|
harness?: string
|