@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 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: 4096;
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: 4096,
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 === 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 === harness && held.id !== scope.subagent?.root && !(scope.subagent && held.session?.startsWith("sub:") && held.session !== fields.session)) {
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 === harness && this.restarts(held, this.placement(scope, now).parent, null)) {
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 !== harness) return new RelayError("unauthorized", `${handle} is a ${held.harness} endpoint: pass --harness ${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 === claimant.harness && spent.spent_cwd !== null && spent.spent_cwd === claimant.cwd;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scp-dev/core",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Swarm Communication Protocol core: wire types, framing, scopes, policy, judge and the relay core.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://scp.dev",
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 === 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 === 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 === 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 !== harness) return new RelayError("unauthorized", `${handle} is a ${held.harness} endpoint: pass --harness ${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 === claimant.harness && spent.spent_cwd !== null && spent.spent_cwd === claimant.cwd
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: 4096,
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