@corenel/harness 0.14.0 → 0.16.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.
Files changed (102) hide show
  1. package/dist/approvals/service.d.ts.map +1 -1
  2. package/dist/approvals/service.js +4 -1
  3. package/dist/approvals/service.js.map +1 -1
  4. package/dist/audit/log.d.ts.map +1 -1
  5. package/dist/audit/log.js +12 -1
  6. package/dist/audit/log.js.map +1 -1
  7. package/dist/audit/recorder.d.ts +7 -1
  8. package/dist/audit/recorder.d.ts.map +1 -1
  9. package/dist/audit/recorder.js +28 -4
  10. package/dist/audit/recorder.js.map +1 -1
  11. package/dist/audit/runAudit.d.ts +2 -1
  12. package/dist/audit/runAudit.d.ts.map +1 -1
  13. package/dist/audit/runAudit.js +2 -0
  14. package/dist/audit/runAudit.js.map +1 -1
  15. package/dist/core/dontsRecent.d.ts +5 -0
  16. package/dist/core/dontsRecent.d.ts.map +1 -0
  17. package/dist/core/dontsRecent.js +23 -0
  18. package/dist/core/dontsRecent.js.map +1 -0
  19. package/dist/core/loop.d.ts +7 -0
  20. package/dist/core/loop.d.ts.map +1 -1
  21. package/dist/core/loop.js +134 -5
  22. package/dist/core/loop.js.map +1 -1
  23. package/dist/core/spawn.d.ts +13 -1
  24. package/dist/core/spawn.d.ts.map +1 -1
  25. package/dist/core/spawn.js +20 -3
  26. package/dist/core/spawn.js.map +1 -1
  27. package/dist/dag/executeNode.d.ts +3 -1
  28. package/dist/dag/executeNode.d.ts.map +1 -1
  29. package/dist/dag/executeNode.js +1 -0
  30. package/dist/dag/executeNode.js.map +1 -1
  31. package/dist/guardrail/permission-service.d.ts.map +1 -1
  32. package/dist/guardrail/permission-service.js +21 -10
  33. package/dist/guardrail/permission-service.js.map +1 -1
  34. package/dist/guardrail/serialize.d.ts.map +1 -1
  35. package/dist/guardrail/serialize.js +5 -0
  36. package/dist/guardrail/serialize.js.map +1 -1
  37. package/dist/guardrail/validate.d.ts.map +1 -1
  38. package/dist/guardrail/validate.js +21 -6
  39. package/dist/guardrail/validate.js.map +1 -1
  40. package/dist/memory/store.d.ts +3 -0
  41. package/dist/memory/store.d.ts.map +1 -1
  42. package/dist/memory/store.js +16 -1
  43. package/dist/memory/store.js.map +1 -1
  44. package/dist/orchestrator/record.d.ts +7 -0
  45. package/dist/orchestrator/record.d.ts.map +1 -1
  46. package/dist/orchestrator/record.js +14 -2
  47. package/dist/orchestrator/record.js.map +1 -1
  48. package/dist/prompts/library/defaults.generated.d.ts +2 -0
  49. package/dist/prompts/library/defaults.generated.d.ts.map +1 -1
  50. package/dist/prompts/library/defaults.generated.js +6 -0
  51. package/dist/prompts/library/defaults.generated.js.map +1 -1
  52. package/dist/prompts/library/index.d.ts +3 -0
  53. package/dist/prompts/library/index.d.ts.map +1 -1
  54. package/dist/prompts/library/index.js +3 -0
  55. package/dist/prompts/library/index.js.map +1 -1
  56. package/dist/sessions/fileSessionStore.d.ts +1 -0
  57. package/dist/sessions/fileSessionStore.d.ts.map +1 -1
  58. package/dist/sessions/fileSessionStore.js +11 -1
  59. package/dist/sessions/fileSessionStore.js.map +1 -1
  60. package/dist/sidecar/accessTestRig.d.ts +32 -0
  61. package/dist/sidecar/accessTestRig.d.ts.map +1 -0
  62. package/dist/sidecar/accessTestRig.js +107 -0
  63. package/dist/sidecar/accessTestRig.js.map +1 -0
  64. package/dist/sidecar/askView.d.ts +20 -0
  65. package/dist/sidecar/askView.d.ts.map +1 -0
  66. package/dist/sidecar/askView.js +38 -0
  67. package/dist/sidecar/askView.js.map +1 -0
  68. package/dist/sidecar/host.d.ts +146 -11
  69. package/dist/sidecar/host.d.ts.map +1 -1
  70. package/dist/sidecar/host.js +406 -13
  71. package/dist/sidecar/host.js.map +1 -1
  72. package/dist/sidecar/hostCapability.d.ts +6 -0
  73. package/dist/sidecar/hostCapability.d.ts.map +1 -0
  74. package/dist/sidecar/hostCapability.js +7 -0
  75. package/dist/sidecar/hostCapability.js.map +1 -0
  76. package/dist/sidecar/identity.d.ts +5 -0
  77. package/dist/sidecar/identity.d.ts.map +1 -1
  78. package/dist/sidecar/memberGate.d.ts +5 -0
  79. package/dist/sidecar/memberGate.d.ts.map +1 -0
  80. package/dist/sidecar/memberGate.js +128 -0
  81. package/dist/sidecar/memberGate.js.map +1 -0
  82. package/dist/sidecar/server.d.ts +425 -11
  83. package/dist/sidecar/server.d.ts.map +1 -1
  84. package/dist/sidecar/server.js +1809 -52
  85. package/dist/sidecar/server.js.map +1 -1
  86. package/dist/skills.d.ts +7 -1
  87. package/dist/skills.d.ts.map +1 -1
  88. package/dist/skills.js +18 -5
  89. package/dist/skills.js.map +1 -1
  90. package/dist/tools/builtins.d.ts +17 -0
  91. package/dist/tools/builtins.d.ts.map +1 -1
  92. package/dist/tools/builtins.js +32 -3
  93. package/dist/tools/builtins.js.map +1 -1
  94. package/dist/tools/callAgent.d.ts +14 -1
  95. package/dist/tools/callAgent.d.ts.map +1 -1
  96. package/dist/tools/callAgent.js +18 -2
  97. package/dist/tools/callAgent.js.map +1 -1
  98. package/dist/tools/createCrewAgent.d.ts +39 -0
  99. package/dist/tools/createCrewAgent.d.ts.map +1 -0
  100. package/dist/tools/createCrewAgent.js +121 -0
  101. package/dist/tools/createCrewAgent.js.map +1 -0
  102. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
- import type { AgentTool, AgentEvent, PermissionDecision, SidecarRoots, DaemonRunActivity, DaemonRegistration, AgentPause, HostSummary, SidecarProcs, SidecarSessions, RoomOpenRequest, RoomSummary, RoomFinding, RoomFindingEdit, ChecksState, WorkspaceLockRef, DaemonApprovalChoice, DaemonApprovalOffer, DaemonApprovalsState, CrewDeployCaller, CrewDeployReply, EcPublicJwk } from '@corenel/protocol';
1
+ import type { AgentTool, AgentEvent, PermissionDecision, SidecarRoots, DaemonRunActivity, DaemonRegistration, AgentPause, HostSummary, SidecarProcs, SidecarSessions, SidecarSpeech, RoomOpenRequest, RoomSummary, RoomFinding, RoomFindingEdit, ChecksState, WorkspaceLockRef, DaemonApprovalChoice, DaemonApprovalOffer, DaemonApprovalsState, CrewDeployCaller, CrewDeployReply, EcPublicJwk, CrewAgentRef, AccessReply, AccessRequestMsg, NodeEvent, McpBinding, MemberReply, MemberRequestMsg, MemberStatus, AccessOverviewRow, AgentCredStatusRow, InactiveDeployment, PersonInfo } from '@corenel/protocol';
2
+ import type { PeerReply, PeerRequestMsg } from '@corenel/protocol';
2
3
  import { type SidecarChannel, type SidecarMsg, type SidecarInfo, type SidecarRunner, type SidecarHostSync, type SidecarConfig, type SidecarAcp, type WebPushSubscription } from '@corenel/protocol';
3
4
  import type { ExplainingIdentityVerifier, SidecarAdmission } from './identity';
4
5
  import { type KeySigner } from './deviceKeys';
@@ -70,6 +71,7 @@ export interface SidecarRunHub {
70
71
  stopReason: string;
71
72
  }): void;
72
73
  error(message: string): void;
74
+ child?(e: NodeEvent): void;
73
75
  }): {
74
76
  replay: {
75
77
  events: AgentEvent[];
@@ -132,6 +134,14 @@ export interface SidecarRooms {
132
134
  * caller renders the reason. */
133
135
  editFinding(roomId: string, participantId: string, edit: RoomFindingEdit): Promise<RoomFinding[]>;
134
136
  subscribe(cb: (room: RoomSummary) => void): () => void;
137
+ /** Team agent access (Part B): may verified user `userId` run EVERY agent
138
+ * participant here, each judged where the daemon actually finds it (the
139
+ * same lookup a room turn runs)? An agent it cannot find is a no. Absent: a
140
+ * team-mode daemon with access wired refuses to open or drive any room. */
141
+ mayRun?(participants: readonly {
142
+ kind: 'agent' | 'human';
143
+ name: string;
144
+ }[], userId: string): Promise<boolean>;
135
145
  }
136
146
  /** The daemon's pause overrides, behind the `agent-pause-*` messages.
137
147
  *
@@ -241,21 +251,38 @@ export interface SidecarDevices {
241
251
  revoke(id: string, now: number): Promise<boolean>;
242
252
  onRevoked(cb: (id: string) => void): () => void;
243
253
  }
244
- /** The daemon's "This machine" standing approvals (plan 3), behind the
245
- * `approvals-*` messages.
254
+ /** The daemon's standing approvals (plan 3), behind the `approvals-*`
255
+ * messages.
246
256
  *
247
257
  * Declared HERE for the same dependency-direction reason as `SidecarPauses`:
248
258
  * packages/sidecar implements it (src/approvals/daemonApprovals.ts).
249
259
  *
250
260
  * `accept`'s `by` is the object THIS connection attached to the attendance
251
- * registry -- the daemon compares it with the client it made the offer to. It
252
- * is never taken from the message, and there is no method that adds a rule
253
- * directly. `accept` throws for a refusal; the reason is sent back. */
261
+ * registry. `userId` on every method is THIS connection's verified user id
262
+ * (spec 2026-10-01 Part C: a team agent's owner accepts and revokes its
263
+ * rules). Neither is ever taken from the message. `ref` is the message's
264
+ * agent reference, validated by the port. There is no method that adds a
265
+ * rule directly. Every method throws for a refusal; the reason is sent back.
266
+ * On a team daemon only an `ApprovalsRefusal`'s reason is: anything else is
267
+ * unexpected, is logged, and goes on the wire as APPROVALS_FAILED. */
254
268
  export interface SidecarApprovals {
255
- accept(by: unknown, suggestionId: string, choice: DaemonApprovalChoice): Promise<void>;
256
- state(): Promise<DaemonApprovalsState>;
257
- revoke(tool: string): Promise<DaemonApprovalsState>;
269
+ accept(by: unknown, suggestionId: string, choice: DaemonApprovalChoice, userId?: string): Promise<void>;
270
+ state(ref?: unknown, userId?: string): Promise<DaemonApprovalsState>;
271
+ revoke(tool: string, ref?: unknown, userId?: string): Promise<DaemonApprovalsState>;
272
+ }
273
+ /** A deliberate refusal from the approvals port, written for the person who
274
+ * asked: its message goes on the wire as is. Recognized by `name`, not by
275
+ * `instanceof`, so a second copy of this module (a test alias, a bundled
276
+ * build) cannot turn a refusal into APPROVALS_FAILED. */
277
+ export declare class ApprovalsRefusal extends Error {
278
+ constructor(message: string);
258
279
  }
280
+ export declare function isApprovalsRefusal(e: unknown): e is ApprovalsRefusal;
281
+ /** Team-mode approvals refusals and the fixed text for an unexpected error
282
+ * (Part C). Exported for the tests. */
283
+ export declare const APPROVALS_FAILED = "Standing approvals could not be read or changed. The daemon log has the reason.";
284
+ export declare const APPROVALS_NO_USER = "In team mode standing approvals need a verified user on this connection.";
285
+ export declare const APPROVALS_NAME_AGENT = "Name the agent: in team mode standing approvals belong to one agent, and only its owner can revoke them.";
259
286
  /** The daemon's orchestrator checks and workspace lock, behind `checks-get`,
260
287
  * `checks-pin` and `workspace-release`. Declared HERE for the same
261
288
  * dependency-direction reason as `SidecarPauses`: packages/sidecar implements
@@ -276,6 +303,36 @@ export interface SidecarChecks {
276
303
  release(ref: WorkspaceLockRef): Promise<boolean>;
277
304
  unavailable?(): string | null;
278
305
  }
306
+ /** Team agent access refusals (Part B). Exported for the tests. */
307
+ export declare const WATCH_REFUSED = "You can watch only runs you started, or unattended runs of an agent you own.";
308
+ export declare const ACCESS_NO_ROLE = "NO_ACCESS: you have no role on this agent, or too low a role for this.";
309
+ export declare const ACCESS_NOT_OWNER = "NOT_OWNER: only this agent's owner or the daemon admin can change it.";
310
+ export declare const ACCESS_ADMIN_ONLY = "NOT_OWNER: only the daemon admin can change the crew folder itself.";
311
+ export declare const ACCESS_STORE_REFUSED = "The access store is never writable.";
312
+ export declare const ACCESS_CHECK_FAILED = "NOT_OWNER: could not check access.";
313
+ export declare const ACCESS_FAILED = "Access could not be checked or changed. The daemon log has the reason.";
314
+ /** An uncoded peer-* failure; the detail goes to the log. One source: the peer admin port reuses it. */
315
+ export declare const PEER_FAILED = "Your team daemon links could not be read or changed. The daemon log has the reason.";
316
+ /** Team run history (spec 2026-10-01 E5.6): an uncoded failure. Fixed text; the daemon log has the detail. */
317
+ export declare const AGENT_RUNS_FAILED = "Team runs could not be read. The daemon log has the reason.";
318
+ /** Part E: a member request the member list could not answer. Fixed text; the
319
+ * daemon log has the detail. */
320
+ export declare const MEMBER_FAILED = "The member request could not be answered. The daemon log has the reason.";
321
+ /** A device store failure. Fixed text; the daemon log has the detail. */
322
+ export declare const DEVICES_FAILED = "The paired devices could not be read or changed. The daemon log has the reason.";
323
+ /** Part D credential refusals (exported for the tests). A refusal with no
324
+ * reply code of its own goes out as fixed text; the daemon log has the detail. */
325
+ export declare const AGENT_CRED_FAILED = "Agent credentials could not be checked or changed. The daemon log has the reason.";
326
+ export declare const AGENT_CRED_LOGIN_FAILED = "The sign-in did not finish. The daemon log has the reason.";
327
+ export declare const AGENT_CRED_SOLO = "This daemon does not keep agent access.";
328
+ export declare const MCP_ADMIN_ONLY = "In team mode only the daemon admin can use or change this machine's MCP servers directly.";
329
+ export declare const SESSION_REFUSED = "In team mode a session is reachable only by the member who created it, or the daemon admin.";
330
+ export declare const SNAPSHOT_REFUSED = "In team mode you can restore only from a snapshot of a session you created.";
331
+ export declare const SNAPSHOT_NAME_REFUSED = "In team mode a snapshot name is chosen by the daemon; only the daemon admin can name one.";
332
+ export declare const SNAPSHOT_USAGE_ADMIN_ONLY = "In team mode only the daemon admin can see this machine's snapshot usage.";
333
+ /** Team mode: a room runs its agents for the person who opens or drives it. */
334
+ export declare const ROOM_RUN_REFUSED = "You need contributor or higher on every agent in this room to open or drive it.";
335
+ export declare const OUTPUT_REFUSED = "You can see the output only of runs you started, or of unattended runs of an agent you own.";
279
336
  /** The daemon's web-push notifier, behind `push-subscribe`/`push-unsubscribe`
280
337
  * (spec 2026-09-24-web-push-design). Declared HERE (harness-side), not
281
338
  * imported from `@corenel/sidecar`, for the same dependency-direction reason
@@ -371,6 +428,43 @@ export interface SidecarMcpAdmin {
371
428
  removeServer(name: string): Promise<void>;
372
429
  setCredential(name: string, header: string, value: string): Promise<void>;
373
430
  }
431
+ /** A team agent's MCP bindings and its own credentials (spec 2026-10-01 Part
432
+ * D), behind `access-bind` and the `agent-cred-*` messages. Declared here for
433
+ * the dependency-direction reason above; packages/sidecar implements it
434
+ * (mcp/agentCreds.ts). `caller` is ALWAYS the connection's verified user.
435
+ * `ref` crosses unvalidated: the implementation validates it, and decides
436
+ * whether `caller` is the agent's owner or the daemon admin. A refusal is a
437
+ * thrown error carrying one of AGENT_CRED_ERROR_CODES as `code`. */
438
+ export interface SidecarAgentCreds {
439
+ bind(ref: unknown, caller: string, server: string, binding: McpBinding | null): Promise<void>;
440
+ setHeaders(ref: unknown, caller: string, server: string, headers: readonly unknown[]): Promise<void>;
441
+ login(ref: unknown, caller: string, server: string, onAuthUrl: (url: string) => void): Promise<{
442
+ ok: true;
443
+ } | {
444
+ ok: false;
445
+ reason: 'no-oauth' | 'no-dcr' | 'failed';
446
+ message: string;
447
+ }>;
448
+ clear(ref: unknown, caller: string, server: string): Promise<void>;
449
+ /** Each MCP server's binding and whether an agent credential is SET (Part E,
450
+ * E2). Presence only. Optional: absent, `agent-cred-status` is UNAVAILABLE. */
451
+ status?(ref: unknown, caller: string): Promise<AgentCredStatusRow[]>;
452
+ }
453
+ /** A member's daemon connected to this (team) daemon, serving its own MCP
454
+ * servers (spec 2026-10-01 Part D). `userId` is the connection's VERIFIED
455
+ * user, never a claim; `urls` are as advertised (the implementation
456
+ * normalizes them). `call` is one `mcp` host-call on that connection and
457
+ * rejects with a HostCallError. */
458
+ export interface SidecarPeerConn {
459
+ userId: string;
460
+ urls: readonly string[];
461
+ call(method: 'list' | 'call', args: unknown[]): Promise<unknown>;
462
+ }
463
+ /** The team daemon's peer routes (packages/sidecar peer/routes.ts). ONE
464
+ * instance for the process, shared by every connection. */
465
+ export interface SidecarPeers {
466
+ attach(conn: SidecarPeerConn): () => void;
467
+ }
374
468
  export interface SidecarMcp {
375
469
  /** Every connected tool, address-ready. Empty when no server is configured
376
470
  * -- that is a normal state, not an error (mirrors `SidecarProcs.list`'s /
@@ -398,7 +492,98 @@ export interface SidecarMcp {
398
492
  isError: boolean;
399
493
  }>;
400
494
  }
495
+ /** What an access check is about. The same members as packages/sidecar's
496
+ * access/service.ts AgentAction, restated with no import for the same
497
+ * dependency-direction reason as `SidecarCrewDeploy`. */
498
+ export type SidecarAgentAction = 'see' | 'run' | 'own-runs' | 'run-list' | 'unattended-content' | 'cancel-any' | 'write-definition' | 'manage-access' | 'manage-credentials' | 'approvals' | 'deploy';
499
+ /** Where a mutating fs tool call lands, when that matters to access: inside
500
+ * one team agent's folder (`isNew`: it has no definition and no access record
501
+ * yet; `whole`: the target IS the agent's folder), on a folder that contains a
502
+ * crew folder, or in the access store itself. */
503
+ export type SidecarInvokeTarget = {
504
+ kind: 'agent';
505
+ ref: CrewAgentRef;
506
+ isNew: boolean;
507
+ whole: boolean;
508
+ } | {
509
+ kind: 'crew-parent';
510
+ } | {
511
+ kind: 'access-store';
512
+ };
513
+ /** The daemon's per-agent access (team agent access, spec 2026-10-01 Part B),
514
+ * behind the `access-*` messages and every access gate in this class.
515
+ *
516
+ * Declared HERE with protocol types only; packages/sidecar implements it
517
+ * (access/port.ts) over its access service, which this package must never
518
+ * import. Every `caller` / `userId` handed to it is THIS connection's verified
519
+ * user, never a message field. */
520
+ export interface SidecarAccess {
521
+ /** --mode team. The gates in this class apply only when true. */
522
+ readonly teamMode: boolean;
523
+ /** The verified user this daemon is signed in as, now; undefined when unknown. */
524
+ adminId(): string | undefined;
525
+ can(ref: CrewAgentRef, userId: string | undefined, action: SidecarAgentAction): Promise<boolean>;
526
+ /** Answer one access-* request. Its ref and fields arrive unvalidated. */
527
+ handle(caller: string | undefined, msg: AccessRequestMsg): Promise<AccessReply>;
528
+ /** The agent has no definition and no access record yet: a write may claim it. */
529
+ isNewAgent(ref: CrewAgentRef): Promise<boolean>;
530
+ onAgentCreated(ref: CrewAgentRef, creator: string | undefined): Promise<void>;
531
+ onAgentDeleted(ref: CrewAgentRef): Promise<void>;
532
+ /** For a mutating fs tool invoked over the wire: what its target touches, or null. */
533
+ invokeTarget(tool: string, args: unknown): Promise<SidecarInvokeTarget | null>;
534
+ /** Team access overview (Part E, E2): one row per team agent `caller` may
535
+ * see. Optional: absent, `access-overview` is refused as NOT_TEAM_AGENT. */
536
+ overview?(caller: string | undefined): Promise<AccessOverviewRow[]>;
537
+ /** Admin only (owner decision 2026-10-06): deployed agents still firing from
538
+ * a removed member's folder. Optional: absent, the reply carries []. */
539
+ inactiveDeployments?(caller: string | undefined): Promise<InactiveDeployment[]>;
540
+ }
541
+ /** Linking this daemon to team daemons (spec 2026-10-01 E5.5). The port
542
+ * decides who may (the daemon's own signed-in account) from the caller the
543
+ * harness hands it, the connection's verified user. */
544
+ export interface SidecarPeerAdmin {
545
+ handle(caller: string | undefined, msg: PeerRequestMsg): Promise<PeerReply>;
546
+ }
547
+ /** Team member status (spec 2026-10-01 Part E, E1), behind the member gate,
548
+ * first sight and the `member-*` messages.
549
+ *
550
+ * Declared HERE with protocol types only; packages/sidecar implements it
551
+ * (members/port.ts). `statusOf` is SYNCHRONOUS because the gate runs on every
552
+ * message: the implementation answers from memory. Every userId handed to it
553
+ * is THIS connection's verified user, never a message field. */
554
+ export interface SidecarMembers {
555
+ /** 'active' or 'pending'. The admin is always active; an unknown or missing
556
+ * user reads as pending (fail closed). */
557
+ statusOf(userId: string | undefined): MemberStatus;
558
+ /** First sight: record a verified user with no record as pending and tell
559
+ * the admin, once per user. Never throws for a known user. */
560
+ noteAdmitted(userId: string, label: string): Promise<void>;
561
+ /** Answer one member-* request. Fields arrive unvalidated. */
562
+ handle(caller: string | undefined, msg: MemberRequestMsg): Promise<MemberReply>;
563
+ /** Status changes: a user approved ('active') or removed ('removed'). */
564
+ subscribe(cb: (userId: string, event: MemberStatus | 'removed') => void): () => void;
565
+ }
566
+ /** Who a verified user IS, for display (spec 2026-10-07 member names). Told
567
+ * the assertion's cleaned name/email once per connection, before first sight.
568
+ * DISPLAY ONLY: the implementation stores it; nothing here decides on it. */
569
+ export interface SidecarPeople {
570
+ note(userId: string, person: PersonInfo): Promise<void>;
571
+ }
572
+ /** How long one run-activity item may take to be decided for one team-mode
573
+ * connection (Task 16b fix round 1). A store that hangs must not leave a
574
+ * client unwelcomed or stall every later item: a timed-out item is dropped
575
+ * for that connection (fail closed). */
576
+ export declare const RUN_ACTIVITY_DECIDE_MS = 3000;
577
+ /** How many per-item deadlines (RUN_ACTIVITY_DECIDE_MS, or its test
578
+ * override) the whole pre-welcome replay may hold the welcome in team mode
579
+ * (final review F14). A replay of many items that each hang would otherwise
580
+ * hold it for one deadline per item. What is not decided by then is dropped
581
+ * for this connection (fail closed). */
582
+ export declare const WELCOME_REPLAY_DEADLINES = 3;
583
+ export declare const WELCOME_REPLAY_MAX_MS: number;
401
584
  export interface SidecarServerOptions {
585
+ /** Overrides RUN_ACTIVITY_DECIDE_MS (tests shorten it). */
586
+ runActivityDecideMs?: number;
402
587
  channel: SidecarChannel;
403
588
  /** The daemon's device store (spec 2026-09-29-device-enrollment-design).
404
589
  * With `sidecarKey`, the ONLY way a connection authenticates: there is no
@@ -459,6 +644,10 @@ export interface SidecarServerOptions {
459
644
  * operator declined shell. Absent means no 'procs' feature is advertised and
460
645
  * every proc message is answered with an error rather than ignored. */
461
646
  procs?: SidecarProcs;
647
+ /** The daemon's local speech model, behind `stt-*`. Present only on a daemon
648
+ * (solo or team) whose speech runtime loaded (packages/sidecar/src/speech). Absent:
649
+ * no 'speech' feature, and `stt-start` is answered with an error. */
650
+ speech?: SidecarSpeech;
462
651
  /** The session layer behind `session-*`. Absent means the daemon runs no
463
652
  * sessions: it advertises no 'sessions' feature and answers every session
464
653
  * message with an error that says so, rather than ignoring them. */
@@ -524,6 +713,25 @@ export interface SidecarServerOptions {
524
713
  * operator who never configured any), and `mcp-call` is refused. */
525
714
  mcp?: SidecarMcp;
526
715
  mcpAdmin?: SidecarMcpAdmin;
716
+ /** Team agents' bindings and credentials (spec 2026-10-01 Part D). Absent:
717
+ * every credential message is refused as UNAVAILABLE. Not in team mode
718
+ * (`access` absent or solo): refused as NOT_TEAM_AGENT. */
719
+ agentCreds?: SidecarAgentCreds;
720
+ /** Peer routes (team side of peer mode, spec 2026-10-01 Part D). */
721
+ peers?: SidecarPeers;
722
+ /** Per-agent access (team agent access, Part B). Absent, or not in team mode:
723
+ * no 'agent-access' feature, every access-* message is refused, and no access
724
+ * gate applies. */
725
+ access?: SidecarAccess;
726
+ /** Team member status (Part E). Wired only in team mode. Absent: no
727
+ * 'team-members' feature, no gate, and member-* is refused as UNAVAILABLE. */
728
+ members?: SidecarMembers;
729
+ /** The people directory (spec 2026-10-07). Absent: names are not recorded
730
+ * and labels stay on device names. Wired in solo and team mode alike. */
731
+ people?: SidecarPeople;
732
+ /** Peer links (spec 2026-10-01 E5.5). Absent: no 'peer-links' feature and
733
+ * every peer-* request is refused. */
734
+ peerAdmin?: SidecarPeerAdmin;
527
735
  /**
528
736
  * Map an authenticated connection to the DURABLE KEY it is filed under.
529
737
  *
@@ -612,6 +820,10 @@ export interface SidecarServerOptions {
612
820
  * (default HELLO_DEADLINE_MS). A socket that never pairs holds daemon
613
821
  * resources for nothing. */
614
822
  helloDeadlineMs?: number;
823
+ /** Deadline for one host-call, in ms (daemon.json `peerCallTimeoutMs`,
824
+ * default DEFAULT_HOST_CALL_TIMEOUT_MS). Not applied to `confirm`/`ask`,
825
+ * which wait on a person. */
826
+ hostCallTimeoutMs?: number;
615
827
  }
616
828
  /** Default pre-pairing deadline: a real client sends `hello` immediately.
617
829
  * It covers the proof exchange AND the identity exchange (IDENTITY_TIMEOUT_MS),
@@ -630,6 +842,22 @@ export declare const IDENTITY_TIMEOUT_MS = 10000;
630
842
  * (`SidecarAdmission.recheckMs`) before it is closed. Generous: the client is
631
843
  * mid-session, and a slow answer is not a refusal. */
632
844
  export declare const RECHECK_TIMEOUT_MS = 30000;
845
+ /** How a host-call failed to produce the host's answer.
846
+ * - `undelivered`: never sent (the connection was already closed, or the send
847
+ * threw). Safe to try on another route.
848
+ * - `timeout` / `closed`: sent, then no answer. The call may have run.
849
+ * - `remote`: the host answered with a refusal; the message is its words. */
850
+ export type HostCallFailure = 'undelivered' | 'timeout' | 'closed' | 'remote';
851
+ export declare class HostCallError extends Error {
852
+ readonly kind: HostCallFailure;
853
+ constructor(kind: HostCallFailure, message: string);
854
+ }
855
+ /** The kind of a host-call rejection, or null for any other error. Structural
856
+ * rather than `instanceof`, so a caller in another package does not depend on
857
+ * sharing this module's copy of the class. */
858
+ export declare function hostCallFailure(e: unknown): HostCallFailure | null;
859
+ /** Default deadline for one host-call (daemon.json `peerCallTimeoutMs`). */
860
+ export declare const DEFAULT_HOST_CALL_TIMEOUT_MS = 60000;
633
861
  /** How long the daemon waits for `client-proof` after its `server-proof`.
634
862
  * The hello deadline closes an unauthenticated socket anyway; this bounds the
635
863
  * wait inside it. */
@@ -639,6 +867,8 @@ export declare class SidecarServer {
639
867
  private readonly opts;
640
868
  private readonly tools;
641
869
  private authed;
870
+ /** This connection's one live dictation stream, if any. */
871
+ private stt;
642
872
  /** Denied hellos on this connection (see MAX_FAILED_HELLOS). */
643
873
  private failedHellos;
644
874
  /** A hello's credential is being checked (the check awaits the device
@@ -691,6 +921,8 @@ export declare class SidecarServer {
691
921
  * connection on a daemon with no `identity` verifier. The token itself is
692
922
  * never kept. */
693
923
  private verifiedUserId;
924
+ /** The verified person's display name/email, set with verifiedUserId. */
925
+ private verifiedPerson;
694
926
  /** The verdict echoed on the welcome, when a verifier is wired. Dead by the
695
927
  * time a welcome is ever sent -- `onHello`'s identity block always
696
928
  * overwrites it with a real reason ('not requested', 'no daemon id', or the
@@ -721,6 +953,24 @@ export declare class SidecarServer {
721
953
  * successful `hello` when `opts.attendance` is wired. Called once, on channel
722
954
  * close, so a dead connection stops receiving/counting toward asks. */
723
955
  private attendedDetach?;
956
+ /** Detach from the peer routes, when this connection serves MCP. */
957
+ private peersDetach?;
958
+ /** This connection's hello announced PEER_DAEMON_FEATURE. Set from the
959
+ * claim, before anything is verified: it only ever TAKES privileges away
960
+ * (no attendance, no broadcasts, only PEER_DAEMON_MESSAGES served, and a
961
+ * welcome with no tools and no root/roots), so a client that claims it
962
+ * falsely only loses its own. It never lifts the member gate's welcome
963
+ * strip (Part E): a pending user who claims it is still told nothing. */
964
+ private peerDaemon;
965
+ /** One log line per peer connection for refused messages, not one each. */
966
+ private peerRefusalLogged;
967
+ /** The tail of this connection's run-activity filter (team agent access,
968
+ * Task 16b). Items are decided one at a time so a run's start, updates and
969
+ * settle reach the client in the order the daemon sent them, however long
970
+ * each one's access check takes. */
971
+ private runActivityChain;
972
+ /** Bumped when the pre-welcome replay is cut off: items queued before it are dropped. */
973
+ private runActivityEpoch;
724
974
  /** The synced directory THIS connection belongs to, once resolved -- the same
725
975
  * value echoed in `welcome.dirName`. Retained so `hosts-list` can stamp
726
976
  * `self` and `hosts-forget` can refuse the caller's own tree from the
@@ -873,8 +1123,11 @@ export declare class SidecarServer {
873
1123
  * message id, so a client correlating by id sees its own stream.
874
1124
  */
875
1125
  private onSubscribe;
876
- /** Stop a run, whoever started it. Same trust as `run`: a connection that may
877
- * start runs on this daemon may stop them. */
1126
+ /** Attach THIS connection to `hub` as a viewer of `m.runId`, replay first. */
1127
+ private watchRun;
1128
+ /** Stop a run. Solo mode: same trust as `run`, a connection that may start
1129
+ * runs on this daemon may stop them. Team mode with per-agent access
1130
+ * (Part B): the run's requester, or its agent's owner or the admin. */
878
1131
  private onCancelRun;
879
1132
  /**
880
1133
  * What this caller's agent has actually done.
@@ -947,6 +1200,15 @@ export declare class SidecarServer {
947
1200
  private onApprovalsAccept;
948
1201
  private onApprovalsList;
949
1202
  private onApprovalsRevoke;
1203
+ /** Team mode: every approvals message needs the user this connection
1204
+ * proved, since a team agent's rules are its owner's to change and its
1205
+ * members' to see. Null when the message may go on to the port. Solo mode
1206
+ * never refuses here. */
1207
+ private approvalsNoUser;
1208
+ /** What an approvals port error puts on the wire. Solo mode: its message,
1209
+ * as always. Team mode: an ApprovalsRefusal's reason, and for anything else
1210
+ * (a path, an errno) fixed text, the detail kept in the daemon log. */
1211
+ private approvalsError;
950
1212
  /** Why the checks port cannot answer right now, or null when it can. */
951
1213
  private checksRefusal;
952
1214
  private onChecksGet;
@@ -965,6 +1227,17 @@ export declare class SidecarServer {
965
1227
  private onRoomList;
966
1228
  private onRoomRead;
967
1229
  private onRoomResume;
1230
+ /** Team agent access (Part B, final review F1): a room runs every agent in
1231
+ * it as the person who opens or drives it, so each one needs `run` for THIS
1232
+ * connection's verified user, decided by the room service where the daemon
1233
+ * actually finds the agent. Null when allowed. Solo mode (or no access
1234
+ * port): null, unchanged. No verified user, no gate on the room service, or
1235
+ * a throw: refused. */
1236
+ private roomRunRefusal;
1237
+ /** `roomRunRefusal` for a room that already exists, by its recorded
1238
+ * participants: the verbs that drive its agents (resume, answer, interject). A room
1239
+ * that is not there is left to the room service's own "No room" answer. */
1240
+ private roomDriveRefusal;
968
1241
  /** WHO THIS CONNECTION IS, INSIDE A ROOM.
969
1242
  *
970
1243
  * The user its pairing JWT proved (`verifiedUserId`: API-signed, aud = this
@@ -1001,13 +1274,151 @@ export declare class SidecarServer {
1001
1274
  * would have produced for the identical address.
1002
1275
  */
1003
1276
  private onMcpAdmin;
1277
+ /** Part D credential messages. The caller is this connection's VERIFIED
1278
+ * user and nothing the message says; the port decides whether that user is
1279
+ * the agent's owner or the admin. Solo mode refuses, as `onAccess` does. The
1280
+ * secret in `agent-cred-set` goes to the port and is never logged or echoed.
1281
+ * A refusal with no reply code goes out as fixed text (Part B's
1282
+ * ACCESS_FAILED rule) and its detail goes to the log. */
1283
+ private onAgentCred;
1004
1284
  private onMcpCall;
1285
+ /** One access-* request, answered for THIS connection's verified user. Solo
1286
+ * mode (or no port) refuses without asking. A port that throws becomes a
1287
+ * refusal: it must never kill the connection or leave the request unanswered. */
1288
+ private onAccess;
1289
+ /** Team mode with a member list, and this connection's verified user is not
1290
+ * an active member right now. Read fresh on every call. Member status
1291
+ * ALONE: a peer daemon connection is exempted from the request gate in
1292
+ * onMessage (it is served only PEER_DAEMON_MESSAGES there), never here, so
1293
+ * a claimed peer cannot lift the welcome strip. A port that throws reads as
1294
+ * pending (fail closed): it runs inside onMessage and every broadcast. */
1295
+ private memberPending;
1296
+ /** One member-* request, answered for THIS connection's verified user. A
1297
+ * port that throws becomes a refusal with fixed text (the detail is
1298
+ * logged), never a dropped connection. */
1299
+ private onMember;
1300
+ /** Team access overview (Part E, E2), for THIS connection's verified user.
1301
+ * Solo mode (or no port) refuses without asking, like onAccess. A coded
1302
+ * refusal passes its code and authored text through; any other failure goes
1303
+ * out as fixed text, with the detail in the log. */
1304
+ private onAccessOverview;
1305
+ /** Each MCP server's binding and credential PRESENCE for one agent (Part E,
1306
+ * E2). The owner or admin only, decided by the port from the verified user.
1307
+ * Solo mode refuses without asking, like onAgentCred. Every row is rebuilt
1308
+ * from its three fields here, so a port that returned more (a header, a
1309
+ * token) could never put it on the wire. */
1310
+ private onAgentCredStatus;
1311
+ /** Team run history (spec 2026-10-01 E5.6): viewer or higher on the agent,
1312
+ * for THIS connection's verified user. The ref is validated before the
1313
+ * access port sees it. Each row is rebuilt from its own fields here
1314
+ * (agentRunRowOf), so a runner that returned more (a message, a
1315
+ * transcript) could never put it on the wire. An uncoded failure is fixed text; the detail stays in the log. */
1316
+ private onAgentRuns;
1317
+ /** peer-* (spec 2026-10-01 E5.5), for THIS connection's verified user. A
1318
+ * link is rebuilt from its five fields, so a port that returned more (a
1319
+ * device key) could never put it on the wire. An uncoded throw is fixed
1320
+ * text; the detail stays in the log. */
1321
+ private onPeer;
1322
+ /** Team mode with per-agent access wired. Every Part B gate keys on this,
1323
+ * so solo mode takes none of the new paths. */
1324
+ private teamAccess;
1325
+ /** Who asked for a run: this process's hub record, the runner's facts, or
1326
+ * the run's stored record (restarts included). */
1327
+ private runRequester;
1328
+ /** May THIS connection read run `runId`'s content (watch it, read its
1329
+ * output)? Decision D5, in this order: a run somebody asked for is theirs
1330
+ * alone, the owner and the admin included; a run nobody asked for (a
1331
+ * schedule, a trigger) is its agent owner's and the admin's. Anything
1332
+ * unknown, and anything that throws, is refused. */
1333
+ private mayReadRun;
1334
+ /** One run-activity item to THIS connection. Solo mode: as is, at once.
1335
+ * Team mode with per-agent access: only what `runActivityFor` lets
1336
+ * through, decided in arrival order. */
1337
+ private sendRunActivity;
1338
+ /** The pre-welcome replay drained, or cut off at its total cap (F14): past
1339
+ * the cap every item still undecided is dropped for this connection, and
1340
+ * the welcome goes out. Never rejects. */
1341
+ private replayDrained;
1342
+ /** `runActivityFor` under a deadline: past it the item is dropped. */
1343
+ private runActivityDecided;
1344
+ /** What THIS connection may hear of one run (team agent access, Part B,
1345
+ * Task 16b). Nothing unless it may list the agent's runs (viewer or higher)
1346
+ * or asked for the run itself. The failure message and the task only when
1347
+ * it may read the run's content (`mayReadRun`, decision D5: the requester,
1348
+ * or for an unattended run the agent's owner or the admin). The agent is
1349
+ * the run's own recorded facts, never the names the item carries. No facts,
1350
+ * facts for a different agent, or any throw: nothing. What gets through says
1351
+ * `mine` on the requester's own connection and `attended` when a person
1352
+ * asked for the run (E5.1); whatever the item claimed of either is dropped,
1353
+ * and no user id is added. */
1354
+ private runActivityFor;
1355
+ /** Team mode: THIS connection's verified user is the daemon admin. A throw
1356
+ * is "no". */
1357
+ private isSessionAdmin;
1358
+ /** The ids of the sessions whose recorded VERIFIED creator is THIS
1359
+ * connection's verified user. Never the claimed `owner`. A session with no
1360
+ * recorded creator is nobody's here. May throw (the caller refuses). */
1361
+ private ownSessionIds;
1362
+ /** May THIS connection act on session `sessionId` (pause, resume, destroy,
1363
+ * capture, watch, run)? Team mode (Task 16c): the admin, or the session's
1364
+ * recorded verified creator. A session with no recorded creator is the
1365
+ * admin's alone. A throw refuses. */
1366
+ private maySession;
1367
+ /** The sessions THIS connection is shown. Solo mode: every one, as before.
1368
+ * Team mode (Task 16c): the admin sees every one, anyone else only their
1369
+ * own. A throw shows none. */
1370
+ private visibleSessions;
1371
+ /** Team mode (Task 16c): why THIS connection may not send `m`, or null.
1372
+ * `session-unwatch` stops only this connection's own watch and is never
1373
+ * refused; `session-snapshots` is filtered where it is answered. */
1374
+ private sessionRefusal;
1375
+ /** May THIS connection stop run `runId`? Its requester may, and so may the
1376
+ * agent's owner or the admin, for any run of that agent. A throw refuses. */
1377
+ private mayCancelRun;
1378
+ /** Who may do this config call on the daemon's own crew (team mode):
1379
+ * - read a definition (or list an agent): any role; read anything else in
1380
+ * an agent's folder (its runs' memory, sessions, logs): owner or admin;
1381
+ * - write, append or delete in an agent's folder: owner or admin, except the
1382
+ * first write into a NEW agent, which claims it for the writer;
1383
+ * - change the crew folder itself or a crew-level file: the admin.
1384
+ * Answers a refusal, or what to record once the call has succeeded. Anything
1385
+ * that cannot be decided, or throws, is a refusal. */
1386
+ private configGate;
1387
+ /** The runner's per-run decision (D5) for one run's kept words on the
1388
+ * daemon crew. No runner decision available, or a throw: no. */
1389
+ private mayReadRunContent;
1390
+ /** A listing without the agents this connection has no role on (team mode),
1391
+ * or null when that cannot be decided. Only whole agents are left out:
1392
+ * dropping individual entries would read as deletions to a syncing client.
1393
+ * The one exception is a run's session folder (`sessions/<runId>`), which
1394
+ * is run content: only the runs this user may read are listed (D5). */
1395
+ private visibleConfigEntries;
1396
+ /** After a config write or delete went through: record a new agent's owner,
1397
+ * or drop a deleted agent's record. Never fails the call that succeeded. */
1398
+ private afterConfigChange;
1399
+ /** Why THIS connection may not run a mutating tool on `target`, or null.
1400
+ * The first write into a new agent claims it, which needs a verified user;
1401
+ * a delete never claims, so it goes through the ordinary check. */
1402
+ private invokeRefusal;
1403
+ /** Team mode: the daemon's MCP servers and credentials are the admin's
1404
+ * (Part B). Null when THIS connection may use or change them. Unknown admin
1405
+ * and any throw while deciding are refusals. */
1406
+ private mcpRefusal;
1005
1407
  private onRoomFindings;
1006
1408
  private onRoomFindingEdit;
1007
1409
  /** One config-channel call. The allowlist is NOT applied here -- it is the
1008
1410
  * service's, daemon-side, where the filesystem is -- so this stays a router
1009
1411
  * and there is exactly one place a path is checked. */
1010
1412
  private onConfig;
1413
+ /** The config path as the filesystem would reach it, for classification:
1414
+ * separators, `.`, empty and `..` segments resolved; the `crew` segment, the
1415
+ * agent folder (to its on-disk name, read from the service's own listing of
1416
+ * `crew`) and a definition file name matched case-insensitively, since the
1417
+ * filesystem under the state dir may be case-insensitive. Null means "cannot
1418
+ * be told" (two case variants exist and none is exact): the caller refuses.
1419
+ * A listing that throws propagates, and the caller refuses. A path outside
1420
+ * the crew comes back as is. */
1421
+ private canonicalConfigPath;
1011
1422
  private onHostSyncPut;
1012
1423
  private onHostSyncDelete;
1013
1424
  /** A ToolContext whose capabilities proxy back to the host (persist-nothing).
@@ -1027,4 +1438,7 @@ export declare class SidecarServer {
1027
1438
  private readonly confirm;
1028
1439
  private readonly ask;
1029
1440
  }
1441
+ /** Whether `user` is the daemon admin `admin`. Both must be non-empty
1442
+ * strings: an unknown admin, or an empty id on either side, is never a match. */
1443
+ export declare function sameAdmin(user: string | undefined, admin: string | undefined): boolean;
1030
1444
  //# sourceMappingURL=server.d.ts.map