@basou/core 0.15.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.
package/dist/index.d.ts CHANGED
@@ -1573,6 +1573,19 @@ type SessionEntry = {
1573
1573
  session: Session;
1574
1574
  suspect: boolean;
1575
1575
  suspectReason: SuspectReason | null;
1576
+ /**
1577
+ * The trail store this entry was read from. Its `sessions` directory locates
1578
+ * the session's `events.jsonl`, so a federated caller can replay events from
1579
+ * the store the session actually lives in (not the local store). For a plain
1580
+ * local load this is the `paths` passed to {@link loadSessionEntries}.
1581
+ */
1582
+ sourceRoot: BasouPaths;
1583
+ /**
1584
+ * Federation host label from the registry (`~/.basou/hosts.yaml`), or `null`
1585
+ * for the local store. Surfaced by orientation so a merged, multi-host view
1586
+ * can attribute the latest session / decision / next-step to its host.
1587
+ */
1588
+ host: string | null;
1576
1589
  };
1577
1590
  /**
1578
1591
  * Per-session degradation reason emitted by {@link loadSessionEntries.onSkip}.
@@ -1595,6 +1608,28 @@ type LoadSessionEntriesOptions = {
1595
1608
  onWarning?: (warning: ReplayWarning, sessionId: string) => void;
1596
1609
  onSkip?: (sessionId: string, reason: SessionSkipReason) => void;
1597
1610
  };
1611
+ /**
1612
+ * A trail store to read in a federated load, tagged with its host label.
1613
+ * `host: null` denotes the local store; a non-null label comes from the host
1614
+ * registry (`~/.basou/hosts.yaml`). `paths` is where that store is reachable
1615
+ * as a local path on this machine (an SSHFS mount, an rsync mirror, etc.) —
1616
+ * basou itself never performs any network I/O to obtain it.
1617
+ */
1618
+ type FederatedRoot = {
1619
+ paths: BasouPaths;
1620
+ host: string | null;
1621
+ };
1622
+ type LoadFederatedOptions = LoadSessionEntriesOptions & {
1623
+ /**
1624
+ * Called when a NON-local root cannot be enumerated (present-but-unreadable
1625
+ * mount, permission error). That root is skipped best-effort so the local
1626
+ * store and other roots still load. The local root (`host: null`) is never
1627
+ * degraded here — its errors propagate, preserving single-store behaviour.
1628
+ * (An absent root path is not an error: {@link enumerateSessionDirs} returns
1629
+ * `[]` on ENOENT, so a dropped mount is simply an empty host.)
1630
+ */
1631
+ onRootUnavailable?: (host: string, error: unknown) => void;
1632
+ };
1598
1633
  /**
1599
1634
  * List session directory names under `paths.sessions`, ULID ascending.
1600
1635
  *
@@ -1668,20 +1703,18 @@ declare function classifySuspect(paths: BasouPaths, sessionId: string, session:
1668
1703
  suspect: boolean;
1669
1704
  suspectReason: SuspectReason | null;
1670
1705
  }>;
1706
+ declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
1671
1707
  /**
1672
- * High-level helper that enumerates session dirs, reads each `session.yaml`,
1673
- * and classifies suspect for `running` sessions in one pass.
1674
- *
1675
- * Per-session degradations are surfaced via `options.onSkip`:
1676
- * - `session_yaml_missing` (ENOENT) and `session_yaml_invalid` (parse or
1677
- * schema violation): the entry is omitted from the result.
1678
- * - `events_jsonl_unreadable`: the entry is still pushed with `suspect=false`
1679
- * so callers can render the session row plus a CLI-side warning.
1680
- *
1681
- * `options.now` is taken once and threaded into every {@link classifySuspect}
1682
- * call so age comparisons are consistent across sessions.
1708
+ * Federated load across multiple trail stores. Each root's sessions are tagged
1709
+ * with that root's host label and `sourceRoot`, so a caller replays events from
1710
+ * the store the session lives in. De-duped by `sessionId` (a per-host random
1711
+ * ULID), then by `source.external_id` when present — first occurrence wins, so
1712
+ * pass the local root FIRST to keep it authoritative (e.g. over a re-imported
1713
+ * copy of the same vendor session on another host). A non-local root that
1714
+ * cannot be enumerated is reported via `onRootUnavailable` and skipped; the
1715
+ * local root's errors propagate, matching {@link loadSessionEntries}.
1683
1716
  */
1684
- declare function loadSessionEntries(paths: BasouPaths, options: LoadSessionEntriesOptions): Promise<SessionEntry[]>;
1717
+ declare function loadFederatedSessionEntries(roots: ReadonlyArray<FederatedRoot>, options: LoadFederatedOptions): Promise<SessionEntry[]>;
1685
1718
 
1686
1719
  type DecisionsRendererInput = {
1687
1720
  paths: BasouPaths;
@@ -3155,6 +3188,19 @@ type OrientationRendererInput = {
3155
3188
  * reads as a verdict for a supervisor, not developer diagnostics.
3156
3189
  */
3157
3190
  verbose?: boolean;
3191
+ /**
3192
+ * Additional trail stores to MERGE into this orientation, each a local path
3193
+ * (an SSHFS mount / rsync mirror of another host's `.basou`) tagged with a
3194
+ * host label. Absent / empty = local-only (byte-identical to before). basou
3195
+ * performs no network I/O; the operator's existing tooling places these paths.
3196
+ */
3197
+ federatedRoots?: FederatedRoot[];
3198
+ /**
3199
+ * Called when a federated (non-local) host root is present but cannot be
3200
+ * enumerated (e.g. an unreadable mount). That host is skipped; the local
3201
+ * store and other hosts still render. An absent root path is silently empty.
3202
+ */
3203
+ onHostUnavailable?: (host: string, error: unknown) => void;
3158
3204
  };
3159
3205
  type OrientationRendererResult = {
3160
3206
  /** Generated body. orientation.md is overwritten whole (no markers, gitignored). */
@@ -3171,11 +3217,13 @@ type DecisionRecord = {
3171
3217
  title: string;
3172
3218
  occurredAt: string;
3173
3219
  sessionId: string;
3220
+ host: string | null;
3174
3221
  };
3175
3222
  type NoteRecord = {
3176
3223
  body: string;
3177
3224
  sessionId: string;
3178
3225
  occurredAt: string;
3226
+ host: string | null;
3179
3227
  };
3180
3228
  type PendingApproval = {
3181
3229
  id: string;
@@ -3200,11 +3248,13 @@ type SuspectSession = {
3200
3248
  sessionId: string;
3201
3249
  status: string;
3202
3250
  reason: SuspectReason | null;
3251
+ host: string | null;
3203
3252
  };
3204
3253
  type LatestSession = {
3205
3254
  sessionId: string;
3206
3255
  label: string | null;
3207
3256
  status: string;
3257
+ host: string | null;
3208
3258
  };
3209
3259
  type SourceCount = {
3210
3260
  kind: string;
@@ -3249,6 +3299,12 @@ type OrientationSummary = {
3249
3299
  plannedTasks: PlannedTask[];
3250
3300
  pendingApprovals: PendingApproval[];
3251
3301
  suspects: SuspectSession[];
3302
+ /**
3303
+ * Distinct non-local host labels present in the merged set (sorted). Empty
3304
+ * for a local-only orientation. Lets a consumer render the multi-host banner
3305
+ * and the local-only-freshness caveat without re-deriving from sessions.
3306
+ */
3307
+ hosts: string[];
3252
3308
  freshness: {
3253
3309
  /** started_at of the newest non-archived session, or null when none captured. */
3254
3310
  newestStartedAt: string | null;
@@ -5324,4 +5380,4 @@ declare function overwriteYamlFile(filePath: string, value: unknown): Promise<vo
5324
5380
  */
5325
5381
  declare const BASOU_CORE_VERSION = "0.1.0";
5326
5382
 
5327
- export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
5383
+ export { ACTIVE_GAP_CAP_MS, type ActiveTimeBasis, type AdapterOutputEvent, type AdoptCandidate, type AdoptCandidateKind, type AppendBasouGitignoreOptions, type AppendBasouGitignoreResult, type AppendEventToExistingInput, type AppendEventToExistingResult, type Approval, type ApprovalApprovedEvent, type ApprovalExpiredEvent, ApprovalIdSchema, type ApprovalLocation, type ApprovalRejectedEvent, type ApprovalRequestedEvent, ApprovalSchema, type ApprovalStatus, ApprovalStatusSchema, type ArchivePlan, type ArchiveTaskInput, type ArchiveTaskResult, type AttachTaskInput, type AttachUpdateTaskStatusInput, type AttachableStatus, BASOU_CORE_VERSION, type BasouPaths, type BulkChainResult, CLAUDE_IMPORT_SOURCE, CODEX_IMPORT_SOURCE, type CaptureMode, type ChainBreakReason, type ChainTailState, type ChainVerdict, type ChainVerdictStatus, type ChainedEvents, ChildProcessRunner, type CitedReview, type ClaudeTranscriptRecord, type ClaudeTranscriptToPayloadOptions, type CodexRolloutRecord, type CodexRolloutToPayloadOptions, type CommandExecutedEvent, type CommandLookup, type CreateAdHocSessionInput, type CreateAdHocSessionResult, type CreateAdHocTaskInput, type CreateManifestInput, type CreateTaskInput, type CreateTaskResult, type DayWorkStats, DecisionIdSchema, type DecisionRecordedEvent, type DecisionsRendererInput, type DecisionsRendererResult, type DeleteTaskInput, type DeleteTaskResult, type DiffResult, type EditTaskInput, type EditTaskResult, type Event, EventIdSchema, EventSchema, EventSourceSchema, type ExistingViewLink, FailedToFinalizeError, type FederatedRoot, type FileChange, type FileChangeStatus, type FileChangedEvent, GENERATED_END, GENERATED_START, type GitSnapshot, type GitSnapshotEvent, type GitignorePlanSummary, type HandoffRendererInput, type HandoffRendererResult, ID_PREFIXES, type IdPrefix, type ImportSessionOptions, type ImportSessionResult, type InstructionFileFact, type InstructionSymlinkFact, type InstructionSymlinkState, IsoTimestampSchema, JSON_SCHEMA_VERSION, type JsonSchemaArtifact, type LoadFederatedOptions, type LoadSessionEntriesOptions, type LoadTaskEntriesOptions, type LoadedApproval, type LockHandle, type LockScope, type Manifest, ManifestSchema, type MarkerSection, type Markers, type MeasureAvailability, type NoteAddedEvent, type OrientationRendererInput, type OrientationRendererResult, type OrientationSummary, PROTOCOL_END, PROTOCOL_START, type PrefixedId, type PresetAction, type PresetCollision, type PresetMarkerConflict, type PresetMarkerKind, type PresetPlanSummary, type PresetRepo, type ProcessRunner, type PublishKind, type PublishTarget, type RechainOptions, type RechainResult, type ReconcileAllResult, type ReconcileAllTasksInput, type ReconcileAllTasksOptions, type ReconcileFailure, type ReconcileResult, type ReconcileTaskInput, type RefreshLinkageInput, type RefreshLinkageResult, type ReimportOptions, type ReimportResult, type RenamePlan, type ReplayOptions, type ReplayWarning, type RepoEntry, type RepoGitignoreFacts, type RepoGitignorePlan, type RepoLanguage, type RepoPresetFacts, type RepoPresetPlan, type RepoSymlinkFacts, type RepoSymlinkPlan, type RepoVisibility, type RepoWiringFacts, type ReportApprovalItem, type ReportData, type ReportDecisionItem, type ReportRendererInput, type ReportRendererResult, type ReportSessionItem, type ReportTaskItem, type ReviewGapRepoSummary, type ReviewGapUnit, type ReviewGapVerdict, type ReviewGapsInput, type ReviewGapsSummary, type RiskLevel, RiskLevelSchema, type RosterAdoptionPlan, type RosterDriftSummary, type RunOptions, type RunResult, STUCK_THRESHOLD_MS, type SanitizePathOptions, type SanitizeRelatedFilesResult, SchemaVersionSchema, type Session, type SessionEndedEvent, type SessionEntry, SessionIdSchema, type SessionImportPayload, SessionImportPayloadSchema, type SessionInnerImportInput, SessionInnerImportSchema, type SessionIntegrity, SessionIntegritySchema, type SessionMetrics, SessionMetricsSchema, SessionSchema, type SessionSkipReason, type SessionSourceKind, SessionSourceKindSchema, type SessionStartedEvent, type SessionStatus, type SessionStatusChangedEvent, SessionStatusSchema, type SessionWorkStats, type SourceRootsReconcile, type SourceWorkStats, type StatusCount, StatusSchema, type StatusSnapshot, type SuspectReason, type SymlinkCollision, type SymlinkConflict, type SymlinkPlanSummary, type Task, type TaskArchivedEvent, type TaskCreatedEvent, type TaskDeletedEvent, type TaskDocument, TaskIdSchema, type TaskLinkageRefreshedEvent, type TaskReconciledEvent, TaskSchema, type TaskSkipReason, type TaskStatus, type TaskStatusChangedEvent, type TaskStatusCount, TaskStatusSchema, TaskWriteAfterEventError, type TaskWriteAfterEventPhase, type TokenTotals, type UpdateAdHocTaskStatusInput, type UpdateTaskStatusInput, type UpdateTaskStatusResult, type ViewCollision, type ViewConflict, type ViewLinkState, type ViewRepoFact, type ViewStrayUnknown, type WiringRisk, type WiringSummary, type WorkStatsInput, type WorkStatsResult, type WorkStatsTotals, WorkspaceIdSchema, type WorkspaceViewPlan, type WriteEventsBulkOptions, type WriteTaskFileMode, acquireLock, appendBasouGitignore, appendChainedEvent, appendChainedEventLocked, appendEvent, appendEventToExistingSession, archiveTask, assertBasouRootSafe, basouPaths, buildJsonSchemas, buildStatusSnapshot, chainEvents, chainRawJsonLines, classifySuspect, claudeCodeAdapterMetadata, claudeTranscriptToImportPayload, codexRolloutToImportPayload, computeWorkStats, createAdHocSessionWithEvent, createManifest, createTaskWithEvent, deleteTask, editTask, ensureBasouDirectory, enumerateApprovals, enumerateArchivedTaskIds, enumerateSessionDirs, enumerateTaskIds, finalizeSessionYaml, findErrorCode, findReviewGaps, formatDurationMs, genesisHash, getDiff, getSnapshot, importSessionFromJson, inspectChainTail, isGitNotFound, isImportDerivedSource, isLazyExpired, isRenderable, isValidPrefixedId, lineHash, linkYamlFile, loadApproval, loadFederatedSessionEntries, loadSessionEntries, loadTaskEntries, normalizeRepoKey, normalizeRepoPath, overwriteYamlFile, parseDuration, parseMarkers, pathBasename, planArchive, planGitignore, planRename, planRosterAdoption, planWorkspaceView, prefixedUlid, readAllEvents, readManifest, readMarkdownFile, readSessionYaml, readStatus, readTaskFile, readTaskFileWithArchiveFallback, readYamlFile, rechainSessionInPlace, reconcileAllTasks, reconcileSourceRoots, reconcileTask, refreshTaskLinkedSessions, reimportPreservingId, removeMarkerSection, renderDecisions, renderHandoff, renderOrientation, renderPresetBlock, renderReport, renderWithMarkers, replayEvents, resolveBasouRepositoryRoot, resolveClaudeCodeCommand, resolveRepositoryRoot, resolveSessionId, resolveTaskId, safeSimpleGit, sanitizePath, sanitizeRelatedFiles, sanitizeWorkingDirectory, serializeEventLine, serializeJsonSchema, sessionWorkStatsFromEvents, summarizeAdapterOutput, summarizeOrientation, summarizePresetPlan, summarizeRosterDrift, summarizeSymlinkPlan, summarizeWiring, tryRemoteUrl, ulid, unknownManifestKeys, updateTaskStatusWithEvent, verifyEventsChain, writeEventsBulk, writeManifest, writeMarkdownFile, writeStatus, writeTaskFile, writeYamlFile };
package/dist/index.js CHANGED
@@ -1372,7 +1372,8 @@ async function classifySuspect(paths, sessionId, session, now, onWarning) {
1372
1372
  }
1373
1373
  return { suspect: false, suspectReason: null };
1374
1374
  }
1375
- async function loadSessionEntries(paths, options) {
1375
+ async function loadEntriesFromRoot(root, options) {
1376
+ const { paths } = root;
1376
1377
  const sessionIds = await enumerateSessionDirs(paths);
1377
1378
  const entries = [];
1378
1379
  for (const sid of sessionIds) {
@@ -1402,10 +1403,50 @@ async function loadSessionEntries(paths, options) {
1402
1403
  } catch {
1403
1404
  options.onSkip?.(sid, "events_jsonl_unreadable");
1404
1405
  }
1405
- entries.push({ sessionId: sid, session, suspect, suspectReason });
1406
+ entries.push({
1407
+ sessionId: sid,
1408
+ session,
1409
+ suspect,
1410
+ suspectReason,
1411
+ sourceRoot: paths,
1412
+ host: root.host
1413
+ });
1406
1414
  }
1407
1415
  return entries;
1408
1416
  }
1417
+ async function loadSessionEntries(paths, options) {
1418
+ return loadEntriesFromRoot({ paths, host: null }, options);
1419
+ }
1420
+ async function loadFederatedSessionEntries(roots, options) {
1421
+ const out = [];
1422
+ const seenIds = /* @__PURE__ */ new Set();
1423
+ const seenExternal = /* @__PURE__ */ new Set();
1424
+ for (const root of roots) {
1425
+ let entries;
1426
+ if (root.host === null) {
1427
+ entries = await loadEntriesFromRoot(root, options);
1428
+ } else {
1429
+ try {
1430
+ entries = await loadEntriesFromRoot(root, options);
1431
+ } catch (error) {
1432
+ options.onRootUnavailable?.(root.host, error);
1433
+ continue;
1434
+ }
1435
+ }
1436
+ for (const entry of entries) {
1437
+ if (seenIds.has(entry.sessionId)) continue;
1438
+ const ext = entry.session.session.source.external_id;
1439
+ if (typeof ext === "string" && ext.length > 0) {
1440
+ const extKey = `${entry.session.session.source.kind}:${ext}`;
1441
+ if (seenExternal.has(extKey)) continue;
1442
+ seenExternal.add(extKey);
1443
+ }
1444
+ seenIds.add(entry.sessionId);
1445
+ out.push(entry);
1446
+ }
1447
+ }
1448
+ return out;
1449
+ }
1409
1450
 
1410
1451
  // src/decisions/decisions-renderer.ts
1411
1452
  async function renderDecisions(input) {
@@ -4344,7 +4385,13 @@ async function summarizeOrientation(input) {
4344
4385
  const loadOpts = { now };
4345
4386
  if (input.onSessionSkip !== void 0) loadOpts.onSkip = input.onSessionSkip;
4346
4387
  if (input.onWarning !== void 0) loadOpts.onWarning = input.onWarning;
4347
- const entries = await loadSessionEntries(input.paths, loadOpts);
4388
+ const entries = input.federatedRoots !== void 0 && input.federatedRoots.length > 0 ? await loadFederatedSessionEntries(
4389
+ [{ paths: input.paths, host: null }, ...input.federatedRoots],
4390
+ {
4391
+ ...loadOpts,
4392
+ ...input.onHostUnavailable !== void 0 ? { onRootUnavailable: input.onHostUnavailable } : {}
4393
+ }
4394
+ ) : await loadSessionEntries(input.paths, loadOpts);
4348
4395
  const decisions = [];
4349
4396
  let latestActivityAt = null;
4350
4397
  let latestNote = null;
@@ -4354,7 +4401,7 @@ async function summarizeOrientation(input) {
4354
4401
  }
4355
4402
  };
4356
4403
  for (const entry of entries) {
4357
- const sessionDir = join14(input.paths.sessions, entry.sessionId);
4404
+ const sessionDir = join14(entry.sourceRoot.sessions, entry.sessionId);
4358
4405
  const counted = entry.session.session.status !== "archived";
4359
4406
  if (counted) noteActivity(entry.session.session.ended_at ?? entry.session.session.started_at);
4360
4407
  try {
@@ -4366,12 +4413,18 @@ async function summarizeOrientation(input) {
4366
4413
  decisionId: ev.decision_id,
4367
4414
  title: ev.title,
4368
4415
  occurredAt: ev.occurred_at,
4369
- sessionId: entry.sessionId
4416
+ sessionId: entry.sessionId,
4417
+ host: entry.host
4370
4418
  });
4371
4419
  }
4372
4420
  if (counted && ev.type === "note_added" && ev.kind === "next_step") {
4373
4421
  if (latestNote === null || Date.parse(ev.occurred_at) > Date.parse(latestNote.occurredAt)) {
4374
- latestNote = { body: ev.body, sessionId: entry.sessionId, occurredAt: ev.occurred_at };
4422
+ latestNote = {
4423
+ body: ev.body,
4424
+ sessionId: entry.sessionId,
4425
+ occurredAt: ev.occurred_at,
4426
+ host: entry.host
4427
+ };
4375
4428
  }
4376
4429
  }
4377
4430
  if (counted) noteActivity(ev.occurred_at);
@@ -4414,7 +4467,8 @@ async function summarizeOrientation(input) {
4414
4467
  const suspects = entries.filter((e) => e.suspect).map((e) => ({
4415
4468
  sessionId: e.sessionId,
4416
4469
  status: e.session.session.status,
4417
- reason: e.suspectReason
4470
+ reason: e.suspectReason,
4471
+ host: e.host
4418
4472
  }));
4419
4473
  const liveEntries = entries.filter(
4420
4474
  (e) => e.session.session.status !== "archived" && e.session.session.source.kind !== "import"
@@ -4423,7 +4477,8 @@ async function summarizeOrientation(input) {
4423
4477
  const latestSession = latestEntry !== void 0 ? {
4424
4478
  sessionId: latestEntry.sessionId,
4425
4479
  label: latestEntry.session.session.label ?? null,
4426
- status: latestEntry.session.session.status
4480
+ status: latestEntry.session.session.status,
4481
+ host: latestEntry.host
4427
4482
  } : null;
4428
4483
  const activityEntries = entries.filter((e) => e.session.session.status !== "archived");
4429
4484
  const newest = [...activityEntries].sort(
@@ -4446,6 +4501,9 @@ async function summarizeOrientation(input) {
4446
4501
  const uniqueFiles = new Set(latestFiles);
4447
4502
  const displayed = [...uniqueFiles].sort().slice(0, limit);
4448
4503
  const overflow = Math.max(0, uniqueFiles.size - limit);
4504
+ const hosts = [
4505
+ ...new Set(entries.map((e) => e.host).filter((h) => h !== null))
4506
+ ].sort();
4449
4507
  return {
4450
4508
  generatedAt: input.nowIso,
4451
4509
  sessionCount: entries.length,
@@ -4458,6 +4516,7 @@ async function summarizeOrientation(input) {
4458
4516
  plannedTasks,
4459
4517
  pendingApprovals,
4460
4518
  suspects,
4519
+ hosts,
4461
4520
  freshness: {
4462
4521
  newestStartedAt: newest?.session.session.started_at ?? null,
4463
4522
  newestSource: newest?.session.session.source.kind ?? null,
@@ -4485,11 +4544,15 @@ function formatOrientationBody(summary, opts) {
4485
4544
  const lines = [];
4486
4545
  const now = new Date(summary.generatedAt);
4487
4546
  const newestRel = relativeAge(summary.freshness.newestStartedAt ?? void 0, now);
4547
+ const hostSuffix = (h) => h !== null ? ` @${h}` : "";
4488
4548
  lines.push("# Orientation");
4489
4549
  lines.push("");
4490
4550
  lines.push(
4491
4551
  `> Generated at ${summary.generatedAt} \xB7 sessions ${summary.sessionCount} \xB7 newest ${newestRel} \xB7 pending ${summary.pendingApprovals.length} \xB7 suspect ${summary.suspects.length}`
4492
4552
  );
4553
+ if (summary.hosts.length > 0) {
4554
+ lines.push(`> hosts: local, ${summary.hosts.join(", ")}`);
4555
+ }
4493
4556
  lines.push("");
4494
4557
  lines.push("## \u4ECA\u3069\u3053\u306B\u3044\u308B");
4495
4558
  lines.push("");
@@ -4497,9 +4560,9 @@ function formatOrientationBody(summary, opts) {
4497
4560
  const s = summary.latestSession;
4498
4561
  const sid = shortId(s.sessionId);
4499
4562
  if (s.label !== null && s.label !== "") {
4500
- lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]`);
4563
+ lines.push(`- \u6700\u7D42 session: ${s.label} (${s.status}) [${sid}]${hostSuffix(s.host)}`);
4501
4564
  } else {
4502
- lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})`);
4565
+ lines.push(`- \u6700\u7D42 session: ${sid} (${s.status})${hostSuffix(s.host)}`);
4503
4566
  }
4504
4567
  } else {
4505
4568
  lines.push("- \u6700\u7D42 session: (no live sessions)");
@@ -4507,7 +4570,9 @@ function formatOrientationBody(summary, opts) {
4507
4570
  if (summary.latestDecision !== null) {
4508
4571
  const dec = summary.latestDecision;
4509
4572
  const decAge = relativeAgeJa(dec.occurredAt, now);
4510
- lines.push(`- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})`);
4573
+ lines.push(
4574
+ `- \u76F4\u8FD1\u306E\u5224\u65AD: ${dec.title} [${shortId(dec.decisionId)}] (${decAge})${hostSuffix(dec.host)}`
4575
+ );
4511
4576
  const activityAt = summary.freshness.latestActivityAt;
4512
4577
  if (activityAt !== null && isTrailingStale(activityAt, dec.occurredAt)) {
4513
4578
  lines.push(
@@ -4562,7 +4627,9 @@ function formatOrientationBody(summary, opts) {
4562
4627
  lines.push("- (none)");
4563
4628
  } else {
4564
4629
  for (const e of summary.suspects) {
4565
- lines.push(`- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}`);
4630
+ lines.push(
4631
+ `- ${shortId(e.sessionId)} (${e.status}) \u2014 ${suspectText(e.reason)}${hostSuffix(e.host)}`
4632
+ );
4566
4633
  }
4567
4634
  }
4568
4635
  lines.push("");
@@ -4571,7 +4638,7 @@ function formatOrientationBody(summary, opts) {
4571
4638
  if (summary.latestNote !== null) {
4572
4639
  const noteAge = relativeAgeJa(summary.latestNote.occurredAt, now);
4573
4640
  lines.push(
4574
- `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]`
4641
+ `- \u6B21\u306E\u8D77\u70B9 (\u8A18\u9332\u6E08\u307F, ${noteAge}): ${noteSummary(summary.latestNote.body)} [session ${shortId(summary.latestNote.sessionId)}]${hostSuffix(summary.latestNote.host)}`
4575
4642
  );
4576
4643
  const activityAt = summary.freshness.latestActivityAt;
4577
4644
  if (activityAt !== null && isTrailingStale(activityAt, summary.latestNote.occurredAt)) {
@@ -4601,6 +4668,12 @@ function formatOrientationBody(summary, opts) {
4601
4668
  lines.push("## \u3053\u308C\u306F\u6700\u65B0\u304B");
4602
4669
  lines.push("");
4603
4670
  for (const line of freshnessVerdict(summary, opts.staleness, now)) lines.push(line);
4671
+ if (summary.hosts.length > 0) {
4672
+ lines.push("");
4673
+ lines.push(
4674
+ "\u6CE8: \u9BAE\u5EA6\u5224\u5B9A\u306F\u3053\u306E\u30DE\u30B7\u30F3\u306E\u30ED\u30FC\u30AB\u30EB\u30B9\u30C8\u30A2\u306E\u307F\u304C\u5BFE\u8C61\u3067\u3059\u3002\u4ED6\u30DB\u30B9\u30C8\u306E\u53D6\u308A\u3053\u307C\u3057\u306F\u5224\u5B9A\u3067\u304D\u307E\u305B\u3093(\u5404\u30DB\u30B9\u30C8\u3067 basou refresh \u3092\u5B9F\u884C\u3057\u540C\u671F\u3057\u3066\u304F\u3060\u3055\u3044)\u3002"
4675
+ );
4676
+ }
4604
4677
  if (opts.verbose) {
4605
4678
  lines.push("");
4606
4679
  lines.push("<!-- verbose: raw freshness telemetry -->");
@@ -4677,8 +4750,9 @@ function freshnessVerdict(summary, staleness, now) {
4677
4750
  "\u6700\u65B0\u304B\u78BA\u8A8D\u3059\u308B\u306B\u306F `basou refresh` \u3092\u5B9F\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002"
4678
4751
  ];
4679
4752
  }
4753
+ const localScope = summary.hosts.length > 0 ? "\u3053\u306E\u30DB\u30B9\u30C8(\u30ED\u30FC\u30AB\u30EB)\u306E" : "";
4680
4754
  const lines = [
4681
- `\u2705 \u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
4755
+ `\u2705 ${localScope}\u53D6\u308A\u8FBC\u307F\u306F\u6700\u65B0\u3067\u3059\u3002\u6700\u5F8C\u306E\u4F5C\u696D\u306F ${rel}(${tool})\u3002\u672A\u53D6\u308A\u8FBC\u307F\u306E native \u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u3042\u308A\u307E\u305B\u3093\u3002`
4682
4756
  ];
4683
4757
  if (suspectCount > 0) {
4684
4758
  lines.push(`\u305F\u3060\u3057\u8981\u6CE8\u610F\u30BB\u30C3\u30B7\u30E7\u30F3\u304C ${suspectCount} \u4EF6\u3042\u308A\u307E\u3059(\u4E0A\u8A18\u300C\u8981\u6CE8\u610F session\u300D\u53C2\u7167)\u3002`);
@@ -7093,6 +7167,7 @@ export {
7093
7167
  lineHash,
7094
7168
  linkYamlFile,
7095
7169
  loadApproval,
7170
+ loadFederatedSessionEntries,
7096
7171
  loadSessionEntries,
7097
7172
  loadTaskEntries,
7098
7173
  normalizeRepoKey,