@ouro.bot/cli 0.1.0-alpha.34 → 0.1.0-alpha.341
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/README.md +188 -187
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
- package/changelog.json +2037 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +117 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +117 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/packets.js +193 -0
- package/dist/arc/presence.js +185 -0
- package/dist/arc/task-lifecycle.js +65 -0
- package/dist/heart/active-work.js +832 -0
- package/dist/heart/agent-entry.js +37 -2
- package/dist/heart/attachments/image-normalize.js +194 -0
- package/dist/heart/attachments/materialize.js +97 -0
- package/dist/heart/attachments/originals.js +88 -0
- package/dist/heart/attachments/render.js +29 -0
- package/dist/heart/attachments/sources/adapter.js +2 -0
- package/dist/heart/attachments/sources/bluebubbles.js +156 -0
- package/dist/heart/attachments/sources/cli-local-file.js +78 -0
- package/dist/heart/attachments/sources/index.js +16 -0
- package/dist/heart/attachments/store.js +103 -0
- package/dist/heart/attachments/types.js +93 -0
- package/dist/heart/auth/auth-flow.js +463 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/bundle-state.js +168 -0
- package/dist/heart/commitments.js +111 -0
- package/dist/heart/config-registry.js +304 -0
- package/dist/heart/config.js +53 -21
- package/dist/heart/core.js +695 -195
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +292 -0
- package/dist/heart/daemon/agent-discovery.js +79 -3
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/agentic-repair.js +170 -0
- package/dist/heart/daemon/bluebubbles-health-diagnostics.js +122 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +591 -0
- package/dist/heart/daemon/cli-exec.js +2302 -0
- package/dist/heart/daemon/cli-help.js +306 -0
- package/dist/heart/daemon/cli-parse.js +824 -0
- package/dist/heart/daemon/cli-render-doctor.js +57 -0
- package/dist/heart/daemon/cli-render.js +512 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +30 -1171
- package/dist/heart/daemon/daemon-entry.js +358 -3
- package/dist/heart/daemon/daemon-health.js +141 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +157 -12
- package/dist/heart/daemon/daemon-tombstone.js +236 -0
- package/dist/heart/daemon/daemon.js +751 -58
- package/dist/heart/daemon/doctor-types.js +8 -0
- package/dist/heart/daemon/doctor.js +427 -0
- package/dist/heart/daemon/health-monitor.js +79 -1
- package/dist/heart/daemon/hooks/agent-config-v2.js +33 -0
- package/dist/heart/daemon/hooks/bundle-meta.js +115 -1
- package/dist/heart/daemon/http-health-probe.js +80 -0
- package/dist/heart/daemon/inner-status.js +89 -0
- package/dist/heart/daemon/interactive-repair.js +91 -0
- package/dist/heart/daemon/launchd.js +46 -9
- package/dist/heart/daemon/log-tailer.js +82 -12
- package/dist/heart/daemon/logs-prune.js +105 -0
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/os-cron-deps.js +134 -0
- package/dist/heart/daemon/ouro-bot-entry.js +1 -1
- package/dist/heart/daemon/process-manager.js +201 -0
- package/dist/heart/daemon/provider-discovery.js +105 -0
- package/dist/heart/daemon/pulse.js +463 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +67 -16
- package/dist/heart/daemon/runtime-metadata.js +101 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/safe-mode.js +161 -0
- package/dist/heart/daemon/sense-manager.js +72 -3
- package/dist/heart/daemon/session-id-resolver.js +131 -0
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +307 -0
- package/dist/heart/daemon/stale-bundle-prune.js +96 -0
- package/dist/heart/daemon/startup-tui.js +237 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +510 -0
- package/dist/heart/daemon/up-progress.js +135 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +140 -0
- package/dist/heart/habits/habit-scheduler.js +371 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +32 -120
- package/dist/heart/{daemon → hatch}/hatch-specialist.js +3 -3
- package/dist/heart/{daemon → hatch}/specialist-prompt.js +10 -7
- package/dist/heart/{daemon → hatch}/specialist-tools.js +49 -3
- package/dist/heart/identity.js +154 -59
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/mcp/mcp-server.js +653 -0
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +59 -0
- package/dist/heart/outlook/outlook-http-hooks.js +64 -0
- package/dist/heart/outlook/outlook-http-response.js +7 -0
- package/dist/heart/outlook/outlook-http-routes.js +232 -0
- package/dist/heart/outlook/outlook-http-static.js +99 -0
- package/dist/heart/outlook/outlook-http-transport.js +116 -0
- package/dist/heart/outlook/outlook-http.js +99 -0
- package/dist/heart/outlook/outlook-read.js +28 -0
- package/dist/heart/outlook/outlook-types.js +27 -0
- package/dist/heart/outlook/outlook-view.js +194 -0
- package/dist/heart/outlook/readers/agent-machine.js +355 -0
- package/dist/heart/outlook/readers/continuity-readers.js +332 -0
- package/dist/heart/outlook/readers/runtime-readers.js +660 -0
- package/dist/heart/outlook/readers/sessions.js +231 -0
- package/dist/heart/outlook/readers/shared.js +111 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +135 -0
- package/dist/heart/provider-models.js +81 -0
- package/dist/heart/provider-ping.js +234 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +171 -50
- package/dist/heart/providers/azure.js +97 -11
- package/dist/heart/providers/error-classification.js +63 -0
- package/dist/heart/providers/github-copilot.js +135 -0
- package/dist/heart/providers/minimax-vlm.js +189 -0
- package/dist/heart/providers/minimax.js +23 -6
- package/dist/heart/providers/openai-codex.js +33 -23
- package/dist/heart/session-activity.js +190 -0
- package/dist/heart/session-events.js +726 -0
- package/dist/heart/session-recall.js +162 -0
- package/dist/heart/start-of-turn-packet.js +341 -0
- package/dist/heart/streaming.js +36 -27
- package/dist/heart/sync.js +332 -0
- package/dist/heart/target-resolution.js +127 -0
- package/dist/heart/tempo.js +93 -0
- package/dist/heart/temporal-view.js +41 -0
- package/dist/heart/tool-activity-callbacks.js +36 -0
- package/dist/heart/tool-description.js +135 -0
- package/dist/heart/tool-friction.js +55 -0
- package/dist/heart/tool-loop.js +200 -0
- package/dist/heart/turn-context.js +358 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-global-installer.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/versioning/ouro-path-installer.js +296 -0
- package/dist/heart/versioning/ouro-version-manager.js +295 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +40 -8
- package/dist/heart/{daemon → versioning}/update-checker.js +12 -2
- package/dist/heart/{daemon → versioning}/update-hooks.js +63 -59
- package/dist/mind/associative-recall.js +137 -66
- package/dist/mind/bundle-manifest.js +7 -1
- package/dist/mind/context.js +89 -93
- package/dist/mind/diary-integrity.js +60 -0
- package/dist/mind/{memory.js → diary.js} +84 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +14 -1
- package/dist/mind/friends/channel.js +56 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/resolver.js +37 -0
- package/dist/mind/friends/store-file.js +58 -3
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/journal-index.js +161 -0
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +74 -7
- package/dist/mind/prompt.js +999 -111
- package/dist/mind/provenance-trust.js +26 -0
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +7 -1
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/file-completeness.js +83 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +224 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/redact.js +79 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-DC7sZefn.js +61 -0
- package/dist/outlook-ui/assets/index-LwChZTgL.css +1 -0
- package/dist/outlook-ui/index.html +15 -0
- package/dist/repertoire/ado-client.js +15 -56
- package/dist/repertoire/ado-semantic.js +11 -10
- package/dist/repertoire/api-client.js +97 -0
- package/dist/repertoire/bitwarden-store.js +319 -0
- package/dist/repertoire/bundle-templates.js +72 -0
- package/dist/repertoire/bw-installer.js +79 -0
- package/dist/repertoire/coding/codex-jsonl.js +64 -0
- package/dist/repertoire/coding/context-pack.js +330 -0
- package/dist/repertoire/coding/feedback.js +197 -30
- package/dist/repertoire/coding/manager.js +158 -9
- package/dist/repertoire/coding/spawner.js +55 -9
- package/dist/repertoire/coding/tools.js +170 -7
- package/dist/repertoire/commerce-errors.js +109 -0
- package/dist/repertoire/commerce-self-test.js +156 -0
- package/dist/repertoire/credential-access.js +527 -0
- package/dist/repertoire/duffel-client.js +185 -0
- package/dist/repertoire/github-client.js +14 -55
- package/dist/repertoire/graph-client.js +11 -52
- package/dist/repertoire/guardrails.js +375 -0
- package/dist/repertoire/mcp-client.js +255 -0
- package/dist/repertoire/mcp-manager.js +305 -0
- package/dist/repertoire/mcp-tools.js +63 -0
- package/dist/repertoire/shell-sessions.js +133 -0
- package/dist/repertoire/skills.js +14 -23
- package/dist/repertoire/stripe-client.js +131 -0
- package/dist/repertoire/tasks/board.js +43 -5
- package/dist/repertoire/tasks/fix.js +182 -0
- package/dist/repertoire/tasks/index.js +28 -10
- package/dist/repertoire/tasks/lifecycle.js +2 -2
- package/dist/repertoire/tasks/parser.js +3 -2
- package/dist/repertoire/tasks/scanner.js +194 -37
- package/dist/repertoire/tasks/transitions.js +16 -79
- package/dist/repertoire/tool-results.js +29 -0
- package/dist/repertoire/tools-attachments.js +316 -0
- package/dist/repertoire/tools-base.js +45 -771
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-bridge.js +141 -0
- package/dist/repertoire/tools-bundle.js +984 -0
- package/dist/repertoire/tools-config.js +185 -0
- package/dist/repertoire/tools-continuity.js +248 -0
- package/dist/repertoire/tools-credential.js +182 -0
- package/dist/repertoire/tools-files.js +342 -0
- package/dist/repertoire/tools-flight.js +224 -0
- package/dist/repertoire/tools-flow.js +105 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-memory.js +376 -0
- package/dist/repertoire/tools-session.js +739 -0
- package/dist/repertoire/tools-shell.js +120 -0
- package/dist/repertoire/tools-stripe.js +180 -0
- package/dist/repertoire/tools-surface.js +243 -0
- package/dist/repertoire/tools-teams.js +12 -62
- package/dist/repertoire/tools-travel.js +125 -0
- package/dist/repertoire/tools-user-profile.js +144 -0
- package/dist/repertoire/tools-vault.js +110 -0
- package/dist/repertoire/tools.js +144 -138
- package/dist/repertoire/travel-api-client.js +360 -0
- package/dist/repertoire/user-profile.js +118 -0
- package/dist/repertoire/vault-setup.js +241 -0
- package/dist/scripts/claude-code-hook.js +41 -0
- package/dist/scripts/claude-code-stop-hook.js +47 -0
- package/dist/senses/attention-queue.js +116 -0
- package/dist/senses/bluebubbles/attachment-cache.js +53 -0
- package/dist/senses/bluebubbles/attachment-download.js +137 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +225 -9
- package/dist/senses/bluebubbles/entry.js +13 -0
- package/dist/senses/bluebubbles/inbound-log.js +113 -0
- package/dist/senses/bluebubbles/index.js +1590 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +121 -70
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +43 -12
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/replay.js +129 -0
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- package/dist/senses/{bluebubbles-session-cleanup.js → bluebubbles/session-cleanup.js} +1 -1
- package/dist/senses/cli/bracketed-paste.js +82 -0
- package/dist/senses/cli/image-paste.js +287 -0
- package/dist/senses/cli/image-ref-navigation.js +75 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/input-keys.js +174 -0
- package/dist/senses/cli/kill-ring.js +86 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +605 -0
- package/dist/senses/cli/spinner-imperative.js +135 -0
- package/dist/senses/cli/spinner.js +101 -0
- package/dist/senses/cli/status-line.js +60 -0
- package/dist/senses/cli/streaming-markdown.js +526 -0
- package/dist/senses/cli/tool-display.js +83 -0
- package/dist/senses/cli/tool-render.js +85 -0
- package/dist/senses/cli/tui-store.js +240 -0
- package/dist/senses/cli/virtual-list.js +35 -0
- package/dist/senses/cli-entry.js +1 -1
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +595 -246
- package/dist/senses/commands.js +65 -1
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/habit-turn-message.js +108 -0
- package/dist/senses/inner-dialog-worker.js +112 -19
- package/dist/senses/inner-dialog.js +633 -86
- package/dist/senses/pipeline.js +567 -0
- package/dist/senses/shared-turn.js +199 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams.js +665 -160
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +29 -7
- package/skills/agent-commerce.md +106 -0
- package/skills/browser-navigation.md +110 -0
- package/skills/commerce-setup-guide.md +116 -0
- package/skills/commerce-setup.md +84 -0
- package/skills/configure-dev-tools.md +81 -0
- package/skills/travel-planning.md +138 -0
- package/dist/heart/daemon/ouro-path-installer.js +0 -178
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -547
- package/dist/senses/debug-activity.js +0 -124
- package/subagents/README.md +0 -73
- package/subagents/work-doer.md +0 -235
- package/subagents/work-merger.md +0 -618
- package/subagents/work-planner.md +0 -382
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/basilisk.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jafar.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/jormungandr.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/kaa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/medusa.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/monty.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/nagini.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/ouroboros.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/python.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/quetzalcoatl.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/sir-hiss.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-serpent.md +0 -0
- /package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/identities/the-snake.md +0 -0
- /package/dist/heart/{daemon → hatch}/hatch-animation.js +0 -0
- /package/dist/heart/{daemon → hatch}/specialist-orchestrator.js +0 -0
- /package/dist/heart/{daemon → versioning}/ouro-uti.js +0 -0
- /package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +0 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared OpenAI embedding provider.
|
|
4
|
+
*
|
|
5
|
+
* Both diary.ts and associative-recall.ts need to call the OpenAI embeddings
|
|
6
|
+
* API. This module provides the shared implementation so neither duplicates
|
|
7
|
+
* the fetch logic.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.OpenAIEmbeddingProvider = void 0;
|
|
11
|
+
exports.createDefaultEmbeddingProvider = createDefaultEmbeddingProvider;
|
|
12
|
+
const config_1 = require("../heart/config");
|
|
13
|
+
const runtime_1 = require("../nerves/runtime");
|
|
14
|
+
const DEFAULT_EMBEDDING_MODEL = "text-embedding-3-small";
|
|
15
|
+
class OpenAIEmbeddingProvider {
|
|
16
|
+
apiKey;
|
|
17
|
+
model;
|
|
18
|
+
constructor(apiKey, model = DEFAULT_EMBEDDING_MODEL) {
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.model = model;
|
|
21
|
+
}
|
|
22
|
+
async embed(texts) {
|
|
23
|
+
const response = await fetch("https://api.openai.com/v1/embeddings", {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: {
|
|
26
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
},
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
model: this.model,
|
|
31
|
+
input: texts,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
throw new Error(`embedding request failed: ${response.status} ${response.statusText}`);
|
|
36
|
+
}
|
|
37
|
+
const payload = (await response.json());
|
|
38
|
+
if (!payload.data || payload.data.length !== texts.length) {
|
|
39
|
+
throw new Error("embedding response missing expected vectors");
|
|
40
|
+
}
|
|
41
|
+
return payload.data.map((entry) => entry.embedding);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
exports.OpenAIEmbeddingProvider = OpenAIEmbeddingProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Create a default embedding provider from the configured API key.
|
|
47
|
+
* Returns null if no key is configured.
|
|
48
|
+
*/
|
|
49
|
+
function createDefaultEmbeddingProvider() {
|
|
50
|
+
const apiKey = (0, config_1.getOpenAIEmbeddingsApiKey)().trim();
|
|
51
|
+
if (!apiKey)
|
|
52
|
+
return null;
|
|
53
|
+
(0, runtime_1.emitNervesEvent)({
|
|
54
|
+
component: "mind",
|
|
55
|
+
event: "mind.embedding_provider_created",
|
|
56
|
+
message: "default embedding provider created",
|
|
57
|
+
meta: { model: DEFAULT_EMBEDDING_MODEL },
|
|
58
|
+
});
|
|
59
|
+
return new OpenAIEmbeddingProvider(apiKey);
|
|
60
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileStateCache = exports.FileStateCache = void 0;
|
|
4
|
+
exports.contentHash = contentHash;
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const runtime_1 = require("../nerves/runtime");
|
|
8
|
+
/** Compute sha256 hex hash of content */
|
|
9
|
+
function contentHash(content) {
|
|
10
|
+
return (0, crypto_1.createHash)("sha256").update(content).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Session-scoped LRU cache tracking file reads.
|
|
14
|
+
* Stores content hashes (not full content) to limit memory.
|
|
15
|
+
* Keyed by absolute file path.
|
|
16
|
+
*
|
|
17
|
+
* Also maintains a separate snapshot list for future rewind support.
|
|
18
|
+
* Snapshots are indexed by content hash and linked to conversation messages.
|
|
19
|
+
*/
|
|
20
|
+
class FileStateCache {
|
|
21
|
+
entries;
|
|
22
|
+
maxSize;
|
|
23
|
+
snapshots = [];
|
|
24
|
+
maxSnapshots = 100;
|
|
25
|
+
constructor(maxSize = 50) {
|
|
26
|
+
this.entries = new Map();
|
|
27
|
+
this.maxSize = maxSize;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Record a file read. Computes content hash and stores metadata.
|
|
31
|
+
*/
|
|
32
|
+
record(filePath, content, mtime, offset, limit, messageId) {
|
|
33
|
+
// If key already exists, delete it so re-insertion moves it to end (most recent)
|
|
34
|
+
if (this.entries.has(filePath)) {
|
|
35
|
+
this.entries.delete(filePath);
|
|
36
|
+
}
|
|
37
|
+
const hash = contentHash(content);
|
|
38
|
+
const fullRead = offset === undefined && limit === undefined;
|
|
39
|
+
this.entries.set(filePath, {
|
|
40
|
+
hash,
|
|
41
|
+
mtime,
|
|
42
|
+
offset: fullRead ? undefined : offset,
|
|
43
|
+
limit: fullRead ? undefined : limit,
|
|
44
|
+
fullRead,
|
|
45
|
+
recordedAt: Date.now(),
|
|
46
|
+
messageId,
|
|
47
|
+
});
|
|
48
|
+
(0, runtime_1.emitNervesEvent)({
|
|
49
|
+
component: "mind",
|
|
50
|
+
event: "mind.file_state.record",
|
|
51
|
+
message: "recorded file state",
|
|
52
|
+
meta: { path: filePath, hash, fullRead },
|
|
53
|
+
});
|
|
54
|
+
// Evict LRU (first entry in Map iteration order) if over capacity
|
|
55
|
+
if (this.entries.size > this.maxSize) {
|
|
56
|
+
const firstKey = this.entries.keys().next().value;
|
|
57
|
+
this.entries.delete(firstKey);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the cached state for a file path. Also promotes it in LRU order.
|
|
62
|
+
*/
|
|
63
|
+
get(filePath) {
|
|
64
|
+
const entry = this.entries.get(filePath);
|
|
65
|
+
if (entry === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
// Promote to most-recently-used by re-inserting
|
|
68
|
+
this.entries.delete(filePath);
|
|
69
|
+
this.entries.set(filePath, entry);
|
|
70
|
+
(0, runtime_1.emitNervesEvent)({
|
|
71
|
+
component: "mind",
|
|
72
|
+
event: "mind.file_state.get",
|
|
73
|
+
message: "retrieved file state",
|
|
74
|
+
meta: { path: filePath, hash: entry.hash },
|
|
75
|
+
});
|
|
76
|
+
return entry;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file has been modified since the last recorded read.
|
|
80
|
+
* Uses mtime as primary signal, content hash as fallback for cloud sync / touch scenarios.
|
|
81
|
+
* Returns { stale: false } if the path is not in cache or the file cannot be stat'd.
|
|
82
|
+
*/
|
|
83
|
+
isStale(filePath) {
|
|
84
|
+
const entry = this.entries.get(filePath);
|
|
85
|
+
if (entry === undefined)
|
|
86
|
+
return { stale: false };
|
|
87
|
+
let currentMtime;
|
|
88
|
+
try {
|
|
89
|
+
currentMtime = (0, fs_1.statSync)(filePath).mtimeMs;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// File doesn't exist or can't be stat'd -- no basis for staleness
|
|
93
|
+
return { stale: false };
|
|
94
|
+
}
|
|
95
|
+
// Fast path: mtime unchanged means not stale
|
|
96
|
+
if (currentMtime === entry.mtime)
|
|
97
|
+
return { stale: false };
|
|
98
|
+
// mtime differs -- check content hash as fallback (handles touch / cloud sync)
|
|
99
|
+
try {
|
|
100
|
+
const currentContent = (0, fs_1.readFileSync)(filePath, "utf-8");
|
|
101
|
+
const currentHash = contentHash(currentContent);
|
|
102
|
+
if (currentHash === entry.hash)
|
|
103
|
+
return { stale: false };
|
|
104
|
+
(0, runtime_1.emitNervesEvent)({
|
|
105
|
+
component: "mind",
|
|
106
|
+
event: "mind.file_state.stale_detected",
|
|
107
|
+
message: "file staleness detected",
|
|
108
|
+
meta: { path: filePath, previousHash: entry.hash, currentHash },
|
|
109
|
+
});
|
|
110
|
+
return { stale: true, reason: `file modified since last read (mtime and content differ)` };
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Can't read file -- treat as not stale (file may have been deleted)
|
|
114
|
+
return { stale: false };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Create a pre-edit snapshot of the current cache state for a file.
|
|
119
|
+
* Snapshots are stored separately from the LRU cache for future rewind support.
|
|
120
|
+
* Returns undefined if the path is not in cache.
|
|
121
|
+
*/
|
|
122
|
+
snapshot(filePath) {
|
|
123
|
+
const entry = this.entries.get(filePath);
|
|
124
|
+
if (entry === undefined)
|
|
125
|
+
return undefined;
|
|
126
|
+
const snap = {
|
|
127
|
+
filePath,
|
|
128
|
+
hash: entry.hash,
|
|
129
|
+
mtime: entry.mtime,
|
|
130
|
+
messageId: entry.messageId,
|
|
131
|
+
createdAt: Date.now(),
|
|
132
|
+
};
|
|
133
|
+
this.snapshots.push(snap);
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "mind",
|
|
136
|
+
event: "mind.file_state.snapshot_created",
|
|
137
|
+
message: "created file state snapshot",
|
|
138
|
+
meta: { path: filePath, hash: snap.hash },
|
|
139
|
+
});
|
|
140
|
+
// Evict oldest snapshots if over capacity
|
|
141
|
+
if (this.snapshots.length > this.maxSnapshots) {
|
|
142
|
+
this.snapshots = this.snapshots.slice(this.snapshots.length - this.maxSnapshots);
|
|
143
|
+
}
|
|
144
|
+
return snap;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Get all snapshots in creation order.
|
|
148
|
+
*/
|
|
149
|
+
getSnapshots() {
|
|
150
|
+
return this.snapshots;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Look up a snapshot by content hash. Returns the first match.
|
|
154
|
+
*/
|
|
155
|
+
lookupSnapshotByHash(hash) {
|
|
156
|
+
return this.snapshots.find(s => s.hash === hash);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Clear all snapshots.
|
|
160
|
+
*/
|
|
161
|
+
clearSnapshots() {
|
|
162
|
+
this.snapshots = [];
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Clear all cached entries (does not clear snapshots).
|
|
166
|
+
*/
|
|
167
|
+
clear() {
|
|
168
|
+
(0, runtime_1.emitNervesEvent)({
|
|
169
|
+
component: "mind",
|
|
170
|
+
event: "mind.file_state.clear",
|
|
171
|
+
message: "cleared file state cache",
|
|
172
|
+
meta: { entryCount: this.entries.size },
|
|
173
|
+
});
|
|
174
|
+
this.entries.clear();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.FileStateCache = FileStateCache;
|
|
178
|
+
/** Session-scoped singleton instance used by tool handlers */
|
|
179
|
+
exports.fileStateCache = new FileStateCache();
|
|
@@ -11,9 +11,22 @@ exports.ONBOARDING_TOKEN_THRESHOLD = 100_000;
|
|
|
11
11
|
function isOnboarding(friend) {
|
|
12
12
|
return (friend.totalTokens ?? 0) < exports.ONBOARDING_TOKEN_THRESHOLD;
|
|
13
13
|
}
|
|
14
|
-
function
|
|
14
|
+
function hasLiveContinuityPressure(state) {
|
|
15
|
+
if (!state)
|
|
16
|
+
return false;
|
|
17
|
+
if (typeof state.currentObligation === "string" && state.currentObligation.trim().length > 0)
|
|
18
|
+
return true;
|
|
19
|
+
if (state.mustResolveBeforeHandoff === true)
|
|
20
|
+
return true;
|
|
21
|
+
if (state.hasQueuedFollowUp === true)
|
|
22
|
+
return true;
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
function getFirstImpressions(friend, state) {
|
|
15
26
|
if (!isOnboarding(friend))
|
|
16
27
|
return "";
|
|
28
|
+
if (hasLiveContinuityPressure(state))
|
|
29
|
+
return "";
|
|
17
30
|
(0, runtime_1.emitNervesEvent)({
|
|
18
31
|
component: "mind",
|
|
19
32
|
event: "mind.first_impressions",
|
|
@@ -2,11 +2,26 @@
|
|
|
2
2
|
// Channel capabilities -- hardcoded const map keyed by channel identifier.
|
|
3
3
|
// Pure lookup, no I/O, cannot fail. Unknown channel gets minimal defaults.
|
|
4
4
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.channelToFacing = channelToFacing;
|
|
5
6
|
exports.getChannelCapabilities = getChannelCapabilities;
|
|
7
|
+
exports.isRemoteChannel = isRemoteChannel;
|
|
8
|
+
exports.getAlwaysOnSenseNames = getAlwaysOnSenseNames;
|
|
6
9
|
const runtime_1 = require("../../nerves/runtime");
|
|
10
|
+
const AGENT_FACING_CHANNELS = new Set(["inner", "mcp"]);
|
|
11
|
+
function channelToFacing(channel) {
|
|
12
|
+
const facing = channel && AGENT_FACING_CHANNELS.has(channel) ? "agent" : "human";
|
|
13
|
+
(0, runtime_1.emitNervesEvent)({
|
|
14
|
+
component: "channels",
|
|
15
|
+
event: "channel.facing_lookup",
|
|
16
|
+
message: "channel facing lookup",
|
|
17
|
+
meta: { channel: channel ?? "undefined", facing },
|
|
18
|
+
});
|
|
19
|
+
return facing;
|
|
20
|
+
}
|
|
7
21
|
const CHANNEL_CAPABILITIES = {
|
|
8
22
|
cli: {
|
|
9
23
|
channel: "cli",
|
|
24
|
+
senseType: "local",
|
|
10
25
|
availableIntegrations: [],
|
|
11
26
|
supportsMarkdown: false,
|
|
12
27
|
supportsStreaming: true,
|
|
@@ -15,6 +30,7 @@ const CHANNEL_CAPABILITIES = {
|
|
|
15
30
|
},
|
|
16
31
|
teams: {
|
|
17
32
|
channel: "teams",
|
|
33
|
+
senseType: "closed",
|
|
18
34
|
availableIntegrations: ["ado", "graph", "github"],
|
|
19
35
|
supportsMarkdown: true,
|
|
20
36
|
supportsStreaming: true,
|
|
@@ -23,15 +39,35 @@ const CHANNEL_CAPABILITIES = {
|
|
|
23
39
|
},
|
|
24
40
|
bluebubbles: {
|
|
25
41
|
channel: "bluebubbles",
|
|
42
|
+
senseType: "open",
|
|
26
43
|
availableIntegrations: [],
|
|
27
44
|
supportsMarkdown: false,
|
|
28
45
|
supportsStreaming: false,
|
|
29
46
|
supportsRichCards: false,
|
|
30
47
|
maxMessageLength: Infinity,
|
|
31
48
|
},
|
|
49
|
+
inner: {
|
|
50
|
+
channel: "inner",
|
|
51
|
+
senseType: "internal",
|
|
52
|
+
availableIntegrations: [],
|
|
53
|
+
supportsMarkdown: false,
|
|
54
|
+
supportsStreaming: true,
|
|
55
|
+
supportsRichCards: false,
|
|
56
|
+
maxMessageLength: Infinity,
|
|
57
|
+
},
|
|
58
|
+
mcp: {
|
|
59
|
+
channel: "mcp",
|
|
60
|
+
senseType: "local",
|
|
61
|
+
availableIntegrations: [],
|
|
62
|
+
supportsMarkdown: true,
|
|
63
|
+
supportsStreaming: false,
|
|
64
|
+
supportsRichCards: false,
|
|
65
|
+
maxMessageLength: Infinity,
|
|
66
|
+
},
|
|
32
67
|
};
|
|
33
68
|
const DEFAULT_CAPABILITIES = {
|
|
34
69
|
channel: "cli",
|
|
70
|
+
senseType: "local",
|
|
35
71
|
availableIntegrations: [],
|
|
36
72
|
supportsMarkdown: false,
|
|
37
73
|
supportsStreaming: false,
|
|
@@ -47,3 +83,23 @@ function getChannelCapabilities(channel) {
|
|
|
47
83
|
});
|
|
48
84
|
return CHANNEL_CAPABILITIES[channel] ?? DEFAULT_CAPABILITIES;
|
|
49
85
|
}
|
|
86
|
+
/** Whether the channel is remote (open or closed) vs local/internal. */
|
|
87
|
+
function isRemoteChannel(capabilities) {
|
|
88
|
+
const senseType = capabilities?.senseType;
|
|
89
|
+
return senseType !== undefined && senseType !== "local" && senseType !== "internal";
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns channel names whose senseType is "open" or "closed" -- i.e. channels
|
|
93
|
+
* that are always-on (daemon-managed) rather than interactive or internal.
|
|
94
|
+
*/
|
|
95
|
+
function getAlwaysOnSenseNames() {
|
|
96
|
+
(0, runtime_1.emitNervesEvent)({
|
|
97
|
+
component: "channels",
|
|
98
|
+
event: "channel.always_on_lookup",
|
|
99
|
+
message: "always-on sense names lookup",
|
|
100
|
+
meta: {},
|
|
101
|
+
});
|
|
102
|
+
return Object.entries(CHANNEL_CAPABILITIES)
|
|
103
|
+
.filter(([, cap]) => cap.senseType === "open" || cap.senseType === "closed")
|
|
104
|
+
.map(([channel]) => channel);
|
|
105
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.upsertGroupContextParticipants = upsertGroupContextParticipants;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
6
|
+
const CURRENT_SCHEMA_VERSION = 1;
|
|
7
|
+
function normalizeDisplayName(externalId, displayName) {
|
|
8
|
+
const trimmed = displayName?.trim();
|
|
9
|
+
return trimmed && trimmed.length > 0 ? trimmed : externalId;
|
|
10
|
+
}
|
|
11
|
+
function buildNameNotes(name, now) {
|
|
12
|
+
return name !== "Unknown"
|
|
13
|
+
? { name: { value: name, savedAt: now } }
|
|
14
|
+
: {};
|
|
15
|
+
}
|
|
16
|
+
function dedupeParticipants(participants) {
|
|
17
|
+
const deduped = new Map();
|
|
18
|
+
for (const participant of participants) {
|
|
19
|
+
const externalId = participant.externalId.trim();
|
|
20
|
+
if (!externalId)
|
|
21
|
+
continue;
|
|
22
|
+
const key = `${participant.provider}:${externalId}`;
|
|
23
|
+
if (!deduped.has(key)) {
|
|
24
|
+
deduped.set(key, {
|
|
25
|
+
...participant,
|
|
26
|
+
externalId,
|
|
27
|
+
displayName: participant.displayName?.trim() || undefined,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return Array.from(deduped.values());
|
|
32
|
+
}
|
|
33
|
+
function createGroupExternalId(provider, groupExternalId, linkedAt) {
|
|
34
|
+
return {
|
|
35
|
+
provider,
|
|
36
|
+
externalId: groupExternalId,
|
|
37
|
+
linkedAt,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function shouldPromoteToAcquaintance(friend) {
|
|
41
|
+
return (friend.trustLevel ?? "stranger") === "stranger";
|
|
42
|
+
}
|
|
43
|
+
function createAcquaintanceRecord(participant, groupExternalId, linkedAt) {
|
|
44
|
+
const name = normalizeDisplayName(participant.externalId, participant.displayName);
|
|
45
|
+
return {
|
|
46
|
+
id: (0, node_crypto_1.randomUUID)(),
|
|
47
|
+
name,
|
|
48
|
+
role: "acquaintance",
|
|
49
|
+
trustLevel: "acquaintance",
|
|
50
|
+
connections: [],
|
|
51
|
+
externalIds: [
|
|
52
|
+
{
|
|
53
|
+
provider: participant.provider,
|
|
54
|
+
externalId: participant.externalId,
|
|
55
|
+
linkedAt,
|
|
56
|
+
},
|
|
57
|
+
createGroupExternalId(participant.provider, groupExternalId, linkedAt),
|
|
58
|
+
],
|
|
59
|
+
tenantMemberships: [],
|
|
60
|
+
toolPreferences: {},
|
|
61
|
+
notes: buildNameNotes(name, linkedAt),
|
|
62
|
+
totalTokens: 0,
|
|
63
|
+
createdAt: linkedAt,
|
|
64
|
+
updatedAt: linkedAt,
|
|
65
|
+
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async function upsertGroupContextParticipants(input) {
|
|
69
|
+
(0, runtime_1.emitNervesEvent)({
|
|
70
|
+
component: "friends",
|
|
71
|
+
event: "friends.group_context_upsert_start",
|
|
72
|
+
message: "upserting shared-group participant context",
|
|
73
|
+
meta: {
|
|
74
|
+
participantCount: input.participants.length,
|
|
75
|
+
hasGroupExternalId: input.groupExternalId.trim().length > 0,
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
const groupExternalId = input.groupExternalId.trim();
|
|
79
|
+
if (!groupExternalId) {
|
|
80
|
+
return [];
|
|
81
|
+
}
|
|
82
|
+
const now = input.now ?? (() => new Date().toISOString());
|
|
83
|
+
const participants = dedupeParticipants(input.participants);
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const participant of participants) {
|
|
86
|
+
const linkedAt = now();
|
|
87
|
+
const existing = await input.store.findByExternalId(participant.provider, participant.externalId);
|
|
88
|
+
if (!existing) {
|
|
89
|
+
const created = createAcquaintanceRecord(participant, groupExternalId, linkedAt);
|
|
90
|
+
await input.store.put(created.id, created);
|
|
91
|
+
results.push({
|
|
92
|
+
friendId: created.id,
|
|
93
|
+
name: created.name,
|
|
94
|
+
trustLevel: "acquaintance",
|
|
95
|
+
created: true,
|
|
96
|
+
updated: false,
|
|
97
|
+
addedGroupExternalId: true,
|
|
98
|
+
});
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const hasGroupExternalId = existing.externalIds.some((externalId) => externalId.externalId === groupExternalId);
|
|
102
|
+
const promoteToAcquaintance = shouldPromoteToAcquaintance(existing);
|
|
103
|
+
const trustLevel = promoteToAcquaintance
|
|
104
|
+
? "acquaintance"
|
|
105
|
+
: existing.trustLevel;
|
|
106
|
+
const role = promoteToAcquaintance
|
|
107
|
+
? "acquaintance"
|
|
108
|
+
: existing.role;
|
|
109
|
+
const updatedExternalIds = hasGroupExternalId
|
|
110
|
+
? existing.externalIds
|
|
111
|
+
: [...existing.externalIds, createGroupExternalId(participant.provider, groupExternalId, linkedAt)];
|
|
112
|
+
const updated = promoteToAcquaintance || !hasGroupExternalId;
|
|
113
|
+
const record = updated
|
|
114
|
+
? {
|
|
115
|
+
...existing,
|
|
116
|
+
role,
|
|
117
|
+
trustLevel,
|
|
118
|
+
externalIds: updatedExternalIds,
|
|
119
|
+
updatedAt: linkedAt,
|
|
120
|
+
}
|
|
121
|
+
: existing;
|
|
122
|
+
if (updated) {
|
|
123
|
+
await input.store.put(record.id, record);
|
|
124
|
+
}
|
|
125
|
+
results.push({
|
|
126
|
+
friendId: record.id,
|
|
127
|
+
name: record.name,
|
|
128
|
+
trustLevel,
|
|
129
|
+
created: false,
|
|
130
|
+
updated,
|
|
131
|
+
addedGroupExternalId: !hasGroupExternalId,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "friends",
|
|
136
|
+
event: "friends.group_context_upsert_end",
|
|
137
|
+
message: "upserted shared-group participant context",
|
|
138
|
+
meta: {
|
|
139
|
+
participantCount: participants.length,
|
|
140
|
+
updatedCount: results.filter((result) => result.created || result.updated).length,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
return results;
|
|
144
|
+
}
|
|
@@ -31,6 +31,43 @@ class FriendResolver {
|
|
|
31
31
|
}
|
|
32
32
|
if (existing)
|
|
33
33
|
return existing;
|
|
34
|
+
// Migration: local provider previously used "${username}@${hostname}" format.
|
|
35
|
+
// If no exact match, try finding a friend with old-format external ID.
|
|
36
|
+
/* v8 ignore start -- migration path: only fires when legacy hostname-format friend exists @preserve */
|
|
37
|
+
if (this.params.provider === "local" && !this.params.externalId.includes("@")) {
|
|
38
|
+
try {
|
|
39
|
+
const all = typeof this.store.listAll === "function" ? await this.store.listAll() : [];
|
|
40
|
+
/* v8 ignore start -- migration path: only fires when legacy hostname-format friend exists @preserve */
|
|
41
|
+
const migrationMatch = all.find((f) => f.externalIds.some((eid) => eid.provider === "local" && eid.externalId.startsWith(this.params.externalId + "@")));
|
|
42
|
+
if (migrationMatch) {
|
|
43
|
+
const now = new Date().toISOString();
|
|
44
|
+
migrationMatch.externalIds.push({
|
|
45
|
+
provider: this.params.provider,
|
|
46
|
+
externalId: this.params.externalId,
|
|
47
|
+
linkedAt: now,
|
|
48
|
+
});
|
|
49
|
+
migrationMatch.updatedAt = now;
|
|
50
|
+
try {
|
|
51
|
+
await this.store.put(migrationMatch.id, migrationMatch);
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// best-effort persist
|
|
55
|
+
}
|
|
56
|
+
(0, runtime_1.emitNervesEvent)({
|
|
57
|
+
component: "friends",
|
|
58
|
+
event: "friends.local_id_migrated",
|
|
59
|
+
message: `migrated local friend identity from hostname format to username-only`,
|
|
60
|
+
meta: { friendId: migrationMatch.id, newExternalId: this.params.externalId },
|
|
61
|
+
});
|
|
62
|
+
return migrationMatch;
|
|
63
|
+
}
|
|
64
|
+
/* v8 ignore stop */
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// fall through to create new
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/* v8 ignore stop */
|
|
34
71
|
// First encounter -- create new FriendRecord
|
|
35
72
|
const now = new Date().toISOString();
|
|
36
73
|
const externalId = {
|
|
@@ -55,10 +55,29 @@ class FileFriendStore {
|
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
async get(id) {
|
|
58
|
+
// Direct UUID lookup
|
|
58
59
|
const record = await this.readJson(path.join(this.friendsPath, `${id}.json`));
|
|
59
|
-
if (
|
|
60
|
-
return
|
|
61
|
-
|
|
60
|
+
if (record)
|
|
61
|
+
return this.normalize(record);
|
|
62
|
+
// Fallback: if id is a name (not UUID), scan for matching friend
|
|
63
|
+
/* v8 ignore start -- name fallback: exercised by live proactive sends @preserve */
|
|
64
|
+
try {
|
|
65
|
+
const entries = await fsPromises.readdir(this.friendsPath);
|
|
66
|
+
for (const entry of entries) {
|
|
67
|
+
if (!entry.endsWith(".json"))
|
|
68
|
+
continue;
|
|
69
|
+
const raw = await this.readJson(path.join(this.friendsPath, entry));
|
|
70
|
+
if (!raw)
|
|
71
|
+
continue;
|
|
72
|
+
const normalized = this.normalize(raw);
|
|
73
|
+
if (normalized.name?.toLowerCase() === id.toLowerCase()) {
|
|
74
|
+
return normalized;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch { /* directory unreadable — return null */ }
|
|
79
|
+
/* v8 ignore stop */
|
|
80
|
+
return null;
|
|
62
81
|
}
|
|
63
82
|
async put(id, record) {
|
|
64
83
|
await this.writeJson(path.join(this.friendsPath, `${id}.json`), this.normalize(record));
|
|
@@ -100,6 +119,25 @@ class FileFriendStore {
|
|
|
100
119
|
}
|
|
101
120
|
return entries.some((entry) => entry.endsWith(".json"));
|
|
102
121
|
}
|
|
122
|
+
async listAll() {
|
|
123
|
+
let entries;
|
|
124
|
+
try {
|
|
125
|
+
entries = await fsPromises.readdir(this.friendsPath);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const records = [];
|
|
131
|
+
for (const entry of entries) {
|
|
132
|
+
if (!entry.endsWith(".json"))
|
|
133
|
+
continue;
|
|
134
|
+
const raw = await this.readJson(path.join(this.friendsPath, entry));
|
|
135
|
+
if (!raw)
|
|
136
|
+
continue;
|
|
137
|
+
records.push(this.normalize(raw));
|
|
138
|
+
}
|
|
139
|
+
return records;
|
|
140
|
+
}
|
|
103
141
|
normalize(raw) {
|
|
104
142
|
const trustLevel = raw.trustLevel;
|
|
105
143
|
const normalizedTrustLevel = trustLevel === "family" ||
|
|
@@ -108,6 +146,8 @@ class FileFriendStore {
|
|
|
108
146
|
trustLevel === "stranger"
|
|
109
147
|
? trustLevel
|
|
110
148
|
: DEFAULT_TRUST_LEVEL;
|
|
149
|
+
const kind = raw.kind === "human" || raw.kind === "agent" ? raw.kind : "human";
|
|
150
|
+
const agentMeta = kind === "agent" ? this.normalizeAgentMeta(raw.agentMeta) : undefined;
|
|
111
151
|
return {
|
|
112
152
|
id: raw.id,
|
|
113
153
|
name: raw.name,
|
|
@@ -134,6 +174,21 @@ class FileFriendStore {
|
|
|
134
174
|
createdAt: typeof raw.createdAt === "string" ? raw.createdAt : new Date().toISOString(),
|
|
135
175
|
updatedAt: typeof raw.updatedAt === "string" ? raw.updatedAt : new Date().toISOString(),
|
|
136
176
|
schemaVersion: typeof raw.schemaVersion === "number" ? raw.schemaVersion : 1,
|
|
177
|
+
kind,
|
|
178
|
+
agentMeta,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
normalizeAgentMeta(raw) {
|
|
182
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
183
|
+
return undefined;
|
|
184
|
+
const meta = raw;
|
|
185
|
+
if (typeof meta.bundleName !== "string")
|
|
186
|
+
return undefined;
|
|
187
|
+
return {
|
|
188
|
+
bundleName: meta.bundleName,
|
|
189
|
+
familiarity: typeof meta.familiarity === "number" ? meta.familiarity : 0,
|
|
190
|
+
sharedMissions: Array.isArray(meta.sharedMissions) ? meta.sharedMissions : [],
|
|
191
|
+
outcomes: Array.isArray(meta.outcomes) ? meta.outcomes : [],
|
|
137
192
|
};
|
|
138
193
|
}
|
|
139
194
|
async readJson(filePath) {
|