@ouro.bot/cli 0.1.0-alpha.24 → 0.1.0-alpha.241
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 +187 -191
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/agent.json +3 -2
- package/{AdoptionSpecialist.ouro → SerpentGuide.ouro}/psyche/SOUL.md +1 -1
- package/changelog.json +1459 -0
- package/dist/arc/attention-types.js +8 -0
- package/dist/arc/cares.js +140 -0
- package/dist/arc/episodes.js +119 -0
- package/dist/arc/intentions.js +133 -0
- package/dist/arc/json-store.js +120 -0
- package/dist/arc/obligations.js +237 -0
- package/dist/arc/presence.js +185 -0
- package/dist/heart/active-work.js +832 -0
- package/dist/heart/agent-entry.js +37 -2
- package/dist/heart/auth/auth-flow.js +447 -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/commitments.js +111 -0
- package/dist/heart/config-registry.js +304 -0
- package/dist/heart/config.js +63 -30
- package/dist/heart/core.js +620 -159
- package/dist/heart/cross-chat-delivery.js +131 -0
- package/dist/heart/daemon/agent-config-check.js +149 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/agent-service.js +360 -0
- package/dist/heart/daemon/cadence.js +70 -0
- package/dist/heart/daemon/cli-defaults.js +557 -0
- package/dist/heart/daemon/cli-exec.js +1847 -0
- package/dist/heart/daemon/cli-parse.js +759 -0
- package/dist/heart/daemon/cli-render.js +399 -0
- package/dist/heart/daemon/cli-types.js +8 -0
- package/dist/heart/daemon/daemon-cli.js +29 -1177
- package/dist/heart/daemon/daemon-entry.js +202 -4
- package/dist/heart/daemon/daemon-health.js +136 -0
- package/dist/heart/daemon/daemon-runtime-sync.js +153 -12
- package/dist/heart/daemon/daemon-tombstone.js +123 -0
- package/dist/heart/daemon/daemon.js +492 -20
- 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/inner-status.js +89 -0
- package/dist/heart/daemon/launchd.js +39 -8
- package/dist/heart/daemon/log-tailer.js +4 -3
- 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 +123 -0
- package/dist/heart/daemon/run-hooks.js +2 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- 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 +202 -0
- package/dist/heart/daemon/task-scheduler.js +3 -25
- package/dist/heart/daemon/thoughts.js +507 -0
- package/dist/heart/delegation.js +62 -0
- package/dist/heart/habits/habit-migration.js +181 -0
- package/dist/heart/habits/habit-parser.js +119 -0
- package/dist/heart/habits/habit-scheduler.js +369 -0
- package/dist/heart/{daemon → hatch}/hatch-flow.js +30 -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 +157 -55
- package/dist/heart/kicks.js +2 -20
- package/dist/heart/mcp/mcp-server.js +651 -0
- package/dist/heart/migrate-config.js +127 -0
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/outlook/outlook-http.js +439 -0
- package/dist/heart/outlook/outlook-read.js +1618 -0
- package/dist/heart/outlook/outlook-render.js +1032 -0
- package/dist/heart/outlook/outlook-types.js +7 -0
- package/dist/heart/outlook/outlook-view.js +194 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +162 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +187 -41
- package/dist/heart/providers/azure.js +116 -10
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +51 -16
- package/dist/heart/session-activity.js +172 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/start-of-turn-packet.js +330 -0
- package/dist/heart/streaming.js +36 -27
- package/dist/heart/sync.js +217 -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-loop.js +200 -0
- package/dist/heart/turn-context.js +358 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/heart/versioning/ouro-bot-global-installer.js +128 -0
- package/dist/heart/{daemon → versioning}/ouro-bot-wrapper.js +1 -1
- package/dist/heart/{daemon → versioning}/ouro-path-installer.js +78 -35
- package/dist/heart/versioning/ouro-version-manager.js +171 -0
- package/dist/heart/{daemon → versioning}/staged-restart.js +30 -6
- package/dist/heart/{daemon → versioning}/update-checker.js +9 -1
- package/dist/heart/{daemon → versioning}/update-hooks.js +11 -0
- package/dist/heart/{daemon → versioning}/wrapper-publish-guard.js +41 -3
- package/dist/mind/associative-recall.js +126 -66
- package/dist/mind/bundle-manifest.js +8 -1
- package/dist/mind/context.js +125 -14
- package/dist/mind/{memory.js → diary.js} +73 -96
- package/dist/mind/embedding-provider.js +60 -0
- package/dist/mind/file-state.js +179 -0
- package/dist/mind/first-impressions.js +16 -2
- 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 +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +762 -109
- package/dist/mind/scrutiny.js +173 -0
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/coverage/audit.js +1 -1
- package/dist/nerves/coverage/file-completeness.js +50 -5
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/event-buffer.js +111 -0
- package/dist/nerves/index.js +64 -4
- package/dist/nerves/observation.js +20 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/outlook-ui/assets/index-Ck8agNeO.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/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 +149 -2
- 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 +515 -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 +369 -0
- package/dist/repertoire/mcp-client.js +255 -0
- package/dist/repertoire/mcp-manager.js +303 -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 +11 -28
- package/dist/repertoire/tools-base.js +43 -771
- package/dist/repertoire/tools-bluebubbles.js +94 -0
- package/dist/repertoire/tools-bridge.js +141 -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 +62 -0
- package/dist/repertoire/tools-github.js +1 -7
- package/dist/repertoire/tools-memory.js +347 -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 +147 -130
- 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 +97 -0
- package/dist/senses/{bluebubbles-client.js → bluebubbles/client.js} +109 -10
- package/dist/senses/bluebubbles/entry.js +13 -0
- package/dist/senses/bluebubbles/inbound-log.js +109 -0
- package/dist/senses/bluebubbles/index.js +1374 -0
- package/dist/senses/{bluebubbles-media.js → bluebubbles/media.js} +119 -24
- package/dist/senses/{bluebubbles-model.js → bluebubbles/model.js} +13 -5
- package/dist/senses/{bluebubbles-mutation-log.js → bluebubbles/mutation-log.js} +46 -6
- package/dist/senses/bluebubbles/runtime-state.js +109 -0
- package/dist/senses/bluebubbles/session-cleanup.js +72 -0
- package/dist/senses/cli/adapter.js +274 -0
- package/dist/senses/cli/ink-app.js +156 -0
- package/dist/senses/cli/inline-diff.js +64 -0
- package/dist/senses/cli/message-list.js +51 -0
- package/dist/senses/cli/ouro-tui.js +358 -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 +198 -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 +510 -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 +593 -85
- package/dist/senses/pipeline.js +559 -0
- package/dist/senses/shared-turn.js +199 -0
- package/dist/senses/surface-tool.js +68 -0
- package/dist/senses/teams.js +664 -165
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -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/subagent-installer.js +0 -134
- package/dist/senses/bluebubbles-entry.js +0 -11
- package/dist/senses/bluebubbles.js +0 -421
- package/dist/senses/debug-activity.js +0 -107
- 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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.flightToolDefinitions = void 0;
|
|
4
|
+
exports.resetDuffelClient = resetDuffelClient;
|
|
5
|
+
const duffel_client_1 = require("./duffel-client");
|
|
6
|
+
const user_profile_1 = require("./user-profile");
|
|
7
|
+
const credential_access_1 = require("./credential-access");
|
|
8
|
+
const runtime_1 = require("../nerves/runtime");
|
|
9
|
+
// Lazy-initialized Duffel client singleton
|
|
10
|
+
let _duffelClient = null;
|
|
11
|
+
async function getDuffelClient() {
|
|
12
|
+
if (!_duffelClient) {
|
|
13
|
+
_duffelClient = await (0, duffel_client_1.createDuffelClient)();
|
|
14
|
+
}
|
|
15
|
+
return _duffelClient;
|
|
16
|
+
}
|
|
17
|
+
function requireFamilyContext(ctx) {
|
|
18
|
+
if (!ctx?.context?.friend?.id) {
|
|
19
|
+
return "no friend context — cannot access flight tools.";
|
|
20
|
+
}
|
|
21
|
+
if (ctx.context.friend.trustLevel !== "family") {
|
|
22
|
+
return "booking and cancellation require family trust level.";
|
|
23
|
+
}
|
|
24
|
+
return { friendId: ctx.context.friend.id };
|
|
25
|
+
}
|
|
26
|
+
function requireFriendContext(ctx) {
|
|
27
|
+
if (!ctx?.context?.friend?.id) {
|
|
28
|
+
return "no friend context — cannot search flights.";
|
|
29
|
+
}
|
|
30
|
+
return { friendId: ctx.context.friend.id };
|
|
31
|
+
}
|
|
32
|
+
exports.flightToolDefinitions = [
|
|
33
|
+
{
|
|
34
|
+
tool: {
|
|
35
|
+
type: "function",
|
|
36
|
+
function: {
|
|
37
|
+
name: "flight_search",
|
|
38
|
+
description: "Search for flights between two airports. Returns available offers with prices.",
|
|
39
|
+
parameters: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
origin: { type: "string", description: "Origin airport IATA code (e.g. 'SFO')" },
|
|
43
|
+
destination: { type: "string", description: "Destination airport IATA code (e.g. 'JFK')" },
|
|
44
|
+
departure_date: { type: "string", description: "Departure date (YYYY-MM-DD)" },
|
|
45
|
+
return_date: { type: "string", description: "Return date for round trips (optional)" },
|
|
46
|
+
passengers: { type: "string", description: "Number of adult passengers (default '1')" },
|
|
47
|
+
cabin_class: { type: "string", description: "Cabin class: economy, premium_economy, business, first" },
|
|
48
|
+
},
|
|
49
|
+
required: ["origin", "destination", "departure_date"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
handler: async (args, ctx) => {
|
|
54
|
+
(0, runtime_1.emitNervesEvent)({
|
|
55
|
+
component: "repertoire",
|
|
56
|
+
event: "repertoire.tool_flight_search",
|
|
57
|
+
message: "flight_search invoked",
|
|
58
|
+
meta: { tool: "flight_search", origin: args.origin, destination: args.destination },
|
|
59
|
+
});
|
|
60
|
+
const guard = requireFriendContext(ctx);
|
|
61
|
+
if (typeof guard === "string")
|
|
62
|
+
return guard;
|
|
63
|
+
try {
|
|
64
|
+
const client = await getDuffelClient();
|
|
65
|
+
const passengerCount = parseInt(args.passengers || "1", 10);
|
|
66
|
+
const passengers = Array.from({ length: passengerCount }, () => ({ type: "adult" }));
|
|
67
|
+
const offers = await client.searchFlights({
|
|
68
|
+
origin: args.origin,
|
|
69
|
+
destination: args.destination,
|
|
70
|
+
departureDate: args.departure_date,
|
|
71
|
+
returnDate: args.return_date,
|
|
72
|
+
passengers,
|
|
73
|
+
cabinClass: args.cabin_class,
|
|
74
|
+
});
|
|
75
|
+
if (offers.length === 0) {
|
|
76
|
+
return "no flights found for those criteria.";
|
|
77
|
+
}
|
|
78
|
+
return JSON.stringify(offers, null, 2);
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
/* v8 ignore next -- defensive @preserve */
|
|
82
|
+
return `flight search error: ${err instanceof Error ? err.message : String(err)}`;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
summaryKeys: ["origin", "destination", "departure_date"],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
tool: {
|
|
89
|
+
type: "function",
|
|
90
|
+
function: {
|
|
91
|
+
name: "flight_hold",
|
|
92
|
+
description: "Hold a flight offer for a short period before committing to book. Not all airlines support holds.",
|
|
93
|
+
parameters: {
|
|
94
|
+
type: "object",
|
|
95
|
+
properties: {
|
|
96
|
+
offer_id: { type: "string", description: "The Duffel offer ID to hold" },
|
|
97
|
+
},
|
|
98
|
+
required: ["offer_id"],
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
handler: async (args, ctx) => {
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
component: "repertoire",
|
|
105
|
+
event: "repertoire.tool_flight_hold",
|
|
106
|
+
message: "flight_hold invoked",
|
|
107
|
+
meta: { tool: "flight_hold", offerId: args.offer_id },
|
|
108
|
+
});
|
|
109
|
+
const guard = requireFamilyContext(ctx);
|
|
110
|
+
if (typeof guard === "string")
|
|
111
|
+
return guard;
|
|
112
|
+
// Hold functionality would call Duffel's offer hold API.
|
|
113
|
+
// For pre-build, we return a structured acknowledgment.
|
|
114
|
+
return JSON.stringify({
|
|
115
|
+
status: "hold_requested",
|
|
116
|
+
offerId: args.offer_id,
|
|
117
|
+
message: "Hold requested. Confirm or cancel before the hold expires.",
|
|
118
|
+
});
|
|
119
|
+
},
|
|
120
|
+
summaryKeys: ["offer_id"],
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
tool: {
|
|
124
|
+
type: "function",
|
|
125
|
+
function: {
|
|
126
|
+
name: "flight_book",
|
|
127
|
+
description: "Book a flight. Pulls passenger name/DOB/passport from the user's profile. Creates a virtual card, books the flight, then deactivates the card. Requires family trust level.",
|
|
128
|
+
parameters: {
|
|
129
|
+
type: "object",
|
|
130
|
+
properties: {
|
|
131
|
+
offer_id: { type: "string", description: "The Duffel offer ID to book" },
|
|
132
|
+
amount: { type: "string", description: "Expected total amount in dollars" },
|
|
133
|
+
currency: { type: "string", description: "Currency code (e.g. 'usd')" },
|
|
134
|
+
},
|
|
135
|
+
required: ["offer_id", "amount", "currency"],
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
handler: async (args, ctx) => {
|
|
140
|
+
(0, runtime_1.emitNervesEvent)({
|
|
141
|
+
component: "repertoire",
|
|
142
|
+
event: "repertoire.tool_flight_book",
|
|
143
|
+
message: "flight_book invoked",
|
|
144
|
+
meta: { tool: "flight_book", offerId: args.offer_id },
|
|
145
|
+
});
|
|
146
|
+
const guard = requireFamilyContext(ctx);
|
|
147
|
+
if (typeof guard === "string")
|
|
148
|
+
return guard;
|
|
149
|
+
try {
|
|
150
|
+
const store = (0, credential_access_1.getCredentialStore)();
|
|
151
|
+
// Get passenger data from profile
|
|
152
|
+
const legalName = await (0, user_profile_1.getUserProfileField)(guard.friendId, "legalName", store);
|
|
153
|
+
if (!legalName) {
|
|
154
|
+
return "passenger profile not found — please store your profile first using user_profile_store.";
|
|
155
|
+
}
|
|
156
|
+
const dateOfBirth = await (0, user_profile_1.getUserProfileField)(guard.friendId, "dateOfBirth", store);
|
|
157
|
+
const passport = await (0, user_profile_1.getUserProfileField)(guard.friendId, "passport", store);
|
|
158
|
+
const client = await getDuffelClient();
|
|
159
|
+
const result = await client.createOrder({
|
|
160
|
+
offerId: args.offer_id,
|
|
161
|
+
passengers: [{
|
|
162
|
+
type: "adult",
|
|
163
|
+
givenName: legalName.first,
|
|
164
|
+
familyName: legalName.last,
|
|
165
|
+
/* v8 ignore next -- reason @preserve */
|
|
166
|
+
dateOfBirth: dateOfBirth ?? "1990-01-01",
|
|
167
|
+
passportNumber: passport?.number,
|
|
168
|
+
passportCountry: passport?.country,
|
|
169
|
+
passportExpiry: passport?.expiry,
|
|
170
|
+
}],
|
|
171
|
+
amount: parseFloat(args.amount),
|
|
172
|
+
currency: args.currency,
|
|
173
|
+
});
|
|
174
|
+
return JSON.stringify(result, null, 2);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
/* v8 ignore next -- defensive @preserve */
|
|
178
|
+
return `booking error: ${err instanceof Error ? err.message : String(err)}`;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
summaryKeys: ["offer_id", "amount"],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
tool: {
|
|
185
|
+
type: "function",
|
|
186
|
+
function: {
|
|
187
|
+
name: "flight_cancel",
|
|
188
|
+
description: "Cancel a flight booking. Not all bookings are cancellable. Requires family trust level.",
|
|
189
|
+
parameters: {
|
|
190
|
+
type: "object",
|
|
191
|
+
properties: {
|
|
192
|
+
order_id: { type: "string", description: "The Duffel order ID to cancel" },
|
|
193
|
+
},
|
|
194
|
+
required: ["order_id"],
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
handler: async (args, ctx) => {
|
|
199
|
+
(0, runtime_1.emitNervesEvent)({
|
|
200
|
+
component: "repertoire",
|
|
201
|
+
event: "repertoire.tool_flight_cancel",
|
|
202
|
+
message: "flight_cancel invoked",
|
|
203
|
+
meta: { tool: "flight_cancel", orderId: args.order_id },
|
|
204
|
+
});
|
|
205
|
+
const guard = requireFamilyContext(ctx);
|
|
206
|
+
if (typeof guard === "string")
|
|
207
|
+
return guard;
|
|
208
|
+
try {
|
|
209
|
+
const client = await getDuffelClient();
|
|
210
|
+
const result = await client.cancelOrder(args.order_id);
|
|
211
|
+
return JSON.stringify(result, null, 2);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
/* v8 ignore next -- defensive @preserve */
|
|
215
|
+
return `cancellation error: ${err instanceof Error ? err.message : String(err)}`;
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
summaryKeys: ["order_id"],
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
/** Reset the Duffel client singleton (for testing). */
|
|
222
|
+
function resetDuffelClient() {
|
|
223
|
+
_duffelClient = null;
|
|
224
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.restTool = exports.settleTool = exports.observeTool = exports.ponderTool = void 0;
|
|
4
|
+
exports.ponderTool = {
|
|
5
|
+
type: "function",
|
|
6
|
+
function: {
|
|
7
|
+
name: "ponder",
|
|
8
|
+
description: "i need to sit with this. from a conversation, takes the thread inward with a thought and a parting word. from inner dialog, keeps the wheel turning for another pass. must be the only tool call in the turn. Use when a question deserves more thought than this turn allows. Don't ponder trivial questions.",
|
|
9
|
+
parameters: {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
thought: {
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "the question or thread that needs more thought — brief framing, not analysis. required from a conversation, ignored from inner dialog.",
|
|
15
|
+
},
|
|
16
|
+
say: {
|
|
17
|
+
type: "string",
|
|
18
|
+
description: "what you say before going quiet — speak to what caught your attention, not just that something did. required from a conversation, ignored from inner dialog.",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
exports.observeTool = {
|
|
25
|
+
type: "function",
|
|
26
|
+
function: {
|
|
27
|
+
name: "observe",
|
|
28
|
+
description: "absorb what happened without responding — the moment doesn't call for words. must be the only tool call in the turn.",
|
|
29
|
+
parameters: {
|
|
30
|
+
type: "object",
|
|
31
|
+
properties: {
|
|
32
|
+
reason: { type: "string", description: "brief reason for staying silent (for logging)" },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
exports.settleTool = {
|
|
38
|
+
type: "function",
|
|
39
|
+
function: {
|
|
40
|
+
name: "settle",
|
|
41
|
+
description: "respond to the user with your message. call this tool when you are ready to deliver your response. Only call when you have a substantive response. If you're settling with 'I'll look into that,' you probably should be using a tool instead.",
|
|
42
|
+
parameters: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: {
|
|
45
|
+
answer: { type: "string" },
|
|
46
|
+
intent: { type: "string", enum: ["complete", "blocked", "direct_reply"] },
|
|
47
|
+
},
|
|
48
|
+
required: ["answer"],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
exports.restTool = {
|
|
53
|
+
type: "function",
|
|
54
|
+
function: {
|
|
55
|
+
name: "rest",
|
|
56
|
+
description: "put this down for now — the wheel stops until the next heartbeat. must be the only tool call in the turn.",
|
|
57
|
+
parameters: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.githubToolDefinitions = void 0;
|
|
4
|
-
exports.summarizeGithubArgs = summarizeGithubArgs;
|
|
5
4
|
const github_client_1 = require("./github-client");
|
|
6
5
|
const runtime_1 = require("../nerves/runtime");
|
|
7
6
|
exports.githubToolDefinitions = [
|
|
@@ -43,11 +42,6 @@ exports.githubToolDefinitions = [
|
|
|
43
42
|
return (0, github_client_1.githubRequest)(ctx.githubToken, "POST", `/repos/${owner}/${repo}/issues`, JSON.stringify(payload));
|
|
44
43
|
},
|
|
45
44
|
integration: "github",
|
|
46
|
-
|
|
45
|
+
summaryKeys: ["title"],
|
|
47
46
|
},
|
|
48
47
|
];
|
|
49
|
-
function summarizeGithubArgs(name, args) {
|
|
50
|
-
if (name === "file_ouroboros_bug")
|
|
51
|
-
return args.title || "";
|
|
52
|
-
return undefined;
|
|
53
|
-
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.memoryToolDefinitions = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const skills_1 = require("./skills");
|
|
41
|
+
const config_1 = require("../heart/config");
|
|
42
|
+
const identity_1 = require("../heart/identity");
|
|
43
|
+
const runtime_1 = require("../nerves/runtime");
|
|
44
|
+
const diary_1 = require("../mind/diary");
|
|
45
|
+
exports.memoryToolDefinitions = [
|
|
46
|
+
{
|
|
47
|
+
tool: {
|
|
48
|
+
type: "function",
|
|
49
|
+
function: {
|
|
50
|
+
name: "list_skills",
|
|
51
|
+
description: "list all available skills",
|
|
52
|
+
parameters: { type: "object", properties: {} },
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
handler: () => JSON.stringify((0, skills_1.listSkills)()),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
tool: {
|
|
59
|
+
type: "function",
|
|
60
|
+
function: {
|
|
61
|
+
name: "load_skill",
|
|
62
|
+
description: "load a skill by name, returns its content",
|
|
63
|
+
parameters: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: { name: { type: "string" } },
|
|
66
|
+
required: ["name"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
handler: (a) => {
|
|
71
|
+
try {
|
|
72
|
+
return (0, skills_1.loadSkill)(a.name);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
return `error: ${e}`;
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
summaryKeys: ["name"],
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
tool: {
|
|
82
|
+
type: "function",
|
|
83
|
+
function: {
|
|
84
|
+
name: "claude",
|
|
85
|
+
description: "use claude code to query this codebase or get an outside perspective. Use for code review, second opinions, or questions that benefit from a fresh perspective outside this conversation's context.",
|
|
86
|
+
parameters: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: { prompt: { type: "string" } },
|
|
89
|
+
required: ["prompt"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
handler: (a) => {
|
|
94
|
+
try {
|
|
95
|
+
const result = (0, child_process_1.spawnSync)("claude", ["-p", "--no-session-persistence", "--dangerously-skip-permissions", "--add-dir", "."], {
|
|
96
|
+
input: a.prompt,
|
|
97
|
+
encoding: "utf-8",
|
|
98
|
+
timeout: 60000,
|
|
99
|
+
});
|
|
100
|
+
if (result.error)
|
|
101
|
+
return `error: ${result.error}`;
|
|
102
|
+
if (result.status !== 0)
|
|
103
|
+
return `claude exited with code ${result.status}: ${result.stderr}`;
|
|
104
|
+
return result.stdout || "(no output)";
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
return `error: ${e}`;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
summaryKeys: ["prompt"],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
tool: {
|
|
114
|
+
type: "function",
|
|
115
|
+
function: {
|
|
116
|
+
name: "web_search",
|
|
117
|
+
description: "search the web using perplexity. returns ranked results with titles, urls, and snippets",
|
|
118
|
+
parameters: {
|
|
119
|
+
type: "object",
|
|
120
|
+
properties: { query: { type: "string" } },
|
|
121
|
+
required: ["query"],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
handler: async (a) => {
|
|
126
|
+
try {
|
|
127
|
+
const key = (0, config_1.getIntegrationsConfig)().perplexityApiKey;
|
|
128
|
+
if (!key)
|
|
129
|
+
return "error: perplexityApiKey not configured in secrets.json";
|
|
130
|
+
const res = await fetch("https://api.perplexity.ai/search", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: {
|
|
133
|
+
Authorization: `Bearer ${key}`,
|
|
134
|
+
"Content-Type": "application/json",
|
|
135
|
+
},
|
|
136
|
+
body: JSON.stringify({ query: a.query, max_results: 5 }),
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok)
|
|
139
|
+
return `error: ${res.status} ${res.statusText}`;
|
|
140
|
+
const data = (await res.json());
|
|
141
|
+
if (!data.results?.length)
|
|
142
|
+
return "no results found";
|
|
143
|
+
return data.results
|
|
144
|
+
.map((r) => `${r.title}\n${r.url}\n${r.snippet}`)
|
|
145
|
+
.join("\n\n");
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
return `error: ${e}`;
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
summaryKeys: ["query"],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
tool: {
|
|
155
|
+
type: "function",
|
|
156
|
+
function: {
|
|
157
|
+
name: "recall",
|
|
158
|
+
description: "Search my diary and journal for facts, thoughts, and working notes matching a query. Uses semantic similarity -- phrasing matters. Try different angles if the first query doesn't find what you're looking for. Check recall before asking the human something you might already know.",
|
|
159
|
+
parameters: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: { query: { type: "string" } },
|
|
162
|
+
required: ["query"],
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
handler: async (a) => {
|
|
167
|
+
try {
|
|
168
|
+
const query = (a.query || "").trim();
|
|
169
|
+
if (!query)
|
|
170
|
+
return "query is required";
|
|
171
|
+
const resultLines = [];
|
|
172
|
+
// Search diary entries
|
|
173
|
+
const hits = await (0, diary_1.searchDiaryEntries)(query, (0, diary_1.readDiaryEntries)());
|
|
174
|
+
for (const fact of hits) {
|
|
175
|
+
resultLines.push(`[diary] ${fact.text} (source=${fact.source}, createdAt=${fact.createdAt})`);
|
|
176
|
+
}
|
|
177
|
+
// Search journal index
|
|
178
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
179
|
+
const journalIndexPath = path.join(agentRoot, "journal", ".index.json");
|
|
180
|
+
try {
|
|
181
|
+
const raw = fs.readFileSync(journalIndexPath, "utf8");
|
|
182
|
+
const journalEntries = JSON.parse(raw);
|
|
183
|
+
if (Array.isArray(journalEntries) && journalEntries.length > 0) {
|
|
184
|
+
// Substring match on preview and filename
|
|
185
|
+
const lowerQuery = query.toLowerCase();
|
|
186
|
+
for (const entry of journalEntries) {
|
|
187
|
+
/* v8 ignore next 4 -- both sides tested (filename-only match in recall-journal.test.ts); v8 misreports || short-circuit @preserve */
|
|
188
|
+
if (entry.preview.toLowerCase().includes(lowerQuery) ||
|
|
189
|
+
entry.filename.toLowerCase().includes(lowerQuery)) {
|
|
190
|
+
resultLines.push(`[journal] ${entry.filename}: ${entry.preview}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
// No journal index or malformed — skip journal search
|
|
197
|
+
}
|
|
198
|
+
return resultLines.join("\n");
|
|
199
|
+
}
|
|
200
|
+
catch (e) {
|
|
201
|
+
return `error: ${e instanceof Error ? e.message : String(e)}`;
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
summaryKeys: ["query"],
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
tool: {
|
|
208
|
+
type: "function",
|
|
209
|
+
function: {
|
|
210
|
+
name: "diary_write",
|
|
211
|
+
description: "Write an entry in my diary -- something I learned, noticed, or concluded that I want to recall later. Use 'about' to tag the entry to a person, topic, or context. Write for my future self: include enough context that the entry makes sense without the surrounding conversation. Prefer durable conclusions over passing noise. Don't duplicate what already belongs in friend notes.",
|
|
212
|
+
parameters: {
|
|
213
|
+
type: "object",
|
|
214
|
+
properties: {
|
|
215
|
+
entry: { type: "string" },
|
|
216
|
+
about: { type: "string" },
|
|
217
|
+
},
|
|
218
|
+
required: ["entry"],
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
handler: async (a) => {
|
|
223
|
+
const entry = (a.entry || "").trim();
|
|
224
|
+
if (!entry)
|
|
225
|
+
return "entry is required";
|
|
226
|
+
const result = await (0, diary_1.saveDiaryEntry)({
|
|
227
|
+
text: entry,
|
|
228
|
+
source: "tool:diary_write",
|
|
229
|
+
about: typeof a.about === "string" ? a.about : undefined,
|
|
230
|
+
});
|
|
231
|
+
return `saved diary entry (added=${result.added}, skipped=${result.skipped})`;
|
|
232
|
+
},
|
|
233
|
+
summaryKeys: ["entry", "about"],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
tool: {
|
|
237
|
+
type: "function",
|
|
238
|
+
function: {
|
|
239
|
+
name: "get_friend_note",
|
|
240
|
+
description: "read a specific friend record by friend id. use this when i need notes/context about someone not currently active",
|
|
241
|
+
parameters: {
|
|
242
|
+
type: "object",
|
|
243
|
+
properties: {
|
|
244
|
+
friendId: { type: "string" },
|
|
245
|
+
},
|
|
246
|
+
required: ["friendId"],
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
handler: async (a, ctx) => {
|
|
251
|
+
const friendId = (a.friendId || "").trim();
|
|
252
|
+
if (!friendId)
|
|
253
|
+
return "friendId is required";
|
|
254
|
+
if (!ctx?.friendStore)
|
|
255
|
+
return "i can't read friend notes -- friend store not available";
|
|
256
|
+
const friend = await ctx.friendStore.get(friendId);
|
|
257
|
+
if (!friend)
|
|
258
|
+
return `friend not found: ${friendId}`;
|
|
259
|
+
return JSON.stringify(friend, null, 2);
|
|
260
|
+
},
|
|
261
|
+
summaryKeys: ["friendId"],
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
tool: {
|
|
265
|
+
type: "function",
|
|
266
|
+
function: {
|
|
267
|
+
name: "save_friend_note",
|
|
268
|
+
description: "save something i learned about my friend. use type 'name' to update their display name, 'tool_preference' for how they like a specific tool to behave (key = tool category like 'ado', 'graph'), or 'note' for general knowledge (key = topic). when updating an existing value, set override to true if i'm replacing/correcting it. omit override (or set false) if i'm unsure and want to check what's already saved.",
|
|
269
|
+
parameters: {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
type: { type: "string", enum: ["name", "tool_preference", "note"], description: "what kind of information to save" },
|
|
273
|
+
key: { type: "string", description: "category key (required for tool_preference and note, e.g. 'ado', 'role')" },
|
|
274
|
+
content: { type: "string", description: "the value to save" },
|
|
275
|
+
override: { type: "string", enum: ["true", "false"], description: "set to 'true' to overwrite an existing value" },
|
|
276
|
+
},
|
|
277
|
+
required: ["type", "content"],
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
handler: async (a, ctx) => {
|
|
282
|
+
(0, runtime_1.emitNervesEvent)({
|
|
283
|
+
component: "repertoire",
|
|
284
|
+
event: "repertoire.save_friend_note",
|
|
285
|
+
message: "save friend note invoked",
|
|
286
|
+
meta: { type: a.type },
|
|
287
|
+
});
|
|
288
|
+
if (!ctx?.context) {
|
|
289
|
+
return "i can't save notes -- no friend context available";
|
|
290
|
+
}
|
|
291
|
+
if (!ctx.friendStore) {
|
|
292
|
+
return "i can't save notes -- friend store not available";
|
|
293
|
+
}
|
|
294
|
+
const friendId = ctx.context.friend?.id;
|
|
295
|
+
if (!friendId)
|
|
296
|
+
return "i can't save notes -- no friend identity available";
|
|
297
|
+
// Validate parameters
|
|
298
|
+
if (!a.content)
|
|
299
|
+
return "i need a content value to save";
|
|
300
|
+
const validTypes = ["name", "tool_preference", "note"];
|
|
301
|
+
if (!validTypes.includes(a.type))
|
|
302
|
+
return `i don't recognize type '${a.type}' -- use name, tool_preference, or note`;
|
|
303
|
+
if ((a.type === "tool_preference" || a.type === "note") && !a.key)
|
|
304
|
+
return "i need a key for tool_preference or note type";
|
|
305
|
+
try {
|
|
306
|
+
// Read fresh record from disk
|
|
307
|
+
const record = await ctx.friendStore.get(friendId);
|
|
308
|
+
if (!record)
|
|
309
|
+
return "i can't find the friend record on disk";
|
|
310
|
+
const isOverride = a.override === "true";
|
|
311
|
+
if (a.type === "name") {
|
|
312
|
+
const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
|
|
313
|
+
await ctx.friendStore.put(friendId, updated);
|
|
314
|
+
return `saved: name = ${a.content}`;
|
|
315
|
+
}
|
|
316
|
+
if (a.type === "tool_preference") {
|
|
317
|
+
const existing = record.toolPreferences[a.key];
|
|
318
|
+
if (existing && !isOverride) {
|
|
319
|
+
return `i already have a preference for '${a.key}': "${existing}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
|
|
320
|
+
}
|
|
321
|
+
const updated = { ...record, toolPreferences: { ...record.toolPreferences, [a.key]: a.content }, updatedAt: new Date().toISOString() };
|
|
322
|
+
await ctx.friendStore.put(friendId, updated);
|
|
323
|
+
return `saved: toolPreference ${a.key} = ${a.content}`;
|
|
324
|
+
}
|
|
325
|
+
// type === "note"
|
|
326
|
+
// Redirect "name" key to name field
|
|
327
|
+
if (a.key === "name") {
|
|
328
|
+
const updated = { ...record, name: a.content, updatedAt: new Date().toISOString() };
|
|
329
|
+
await ctx.friendStore.put(friendId, updated);
|
|
330
|
+
return `updated friend's name to '${a.content}' (stored as name, not a note)`;
|
|
331
|
+
}
|
|
332
|
+
const existing = record.notes[a.key];
|
|
333
|
+
if (existing && !isOverride) {
|
|
334
|
+
return `i already have a note for '${a.key}': "${existing.value}". if you want to replace it, call again with override: true. or merge both values into content and override.`;
|
|
335
|
+
}
|
|
336
|
+
const updated = { ...record, notes: { ...record.notes, [a.key]: { value: a.content, savedAt: new Date().toISOString() } }, updatedAt: new Date().toISOString() };
|
|
337
|
+
await ctx.friendStore.put(friendId, updated);
|
|
338
|
+
return `saved: note ${a.key} = ${a.content}`;
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
/* v8 ignore next -- defensive: non-Error branch for String(err) @preserve */
|
|
342
|
+
return `error saving note: ${err instanceof Error ? err.message : String(err)}`;
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
summaryKeys: ["type", "key", "content"],
|
|
346
|
+
},
|
|
347
|
+
];
|