@ouro.bot/cli 0.1.0-alpha.742 → 0.1.0-alpha.744
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/changelog.json +32 -0
- package/deploy/unraid/Dockerfile +7 -2
- package/deploy/unraid/README.txt +704 -64
- package/deploy/unraid/migrate-sanctuary-bundle.mjs +21 -0
- package/deploy/unraid/ouro-events/bootstrap-spool.sh +92 -0
- package/deploy/unraid/ouro-events/emit-event.mjs +310 -0
- package/deploy/unraid/ouro-events/emit-usenet-event.sh +54 -0
- package/deploy/unraid/ouro-events/install-usenet-guard.sh +434 -0
- package/deploy/unraid/ouro-events/usenet-health.sh +178 -0
- package/deploy/unraid/sanctuary-acceptance-contract.json +4 -3
- package/deploy/unraid/sanctuary-unit16-host-broker.mjs +5 -1
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +2 -2
- package/deploy/unraid/sanctuary.ouro/habits/sanctuary-health.md +1 -1
- package/deploy/unraid/sanctuary.ouro/psyche/ASPIRATIONS.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/IDENTITY.md +3 -1
- package/deploy/unraid/sanctuary.ouro/psyche/LORE.md +4 -0
- package/deploy/unraid/sanctuary.ouro/psyche/SOUL.md +6 -2
- package/deploy/unraid/sanctuary.ouro/psyche/TACIT.md +4 -2
- package/deploy/unraid/sanctuary.ouro/state/policy/steward.json +7 -0
- package/deploy/unraid/sanctuary.ouro/tool-profiles.json +19 -17
- package/deploy/unraid/sanctuary.xml +13 -10
- package/dist/arc/cares.js +171 -37
- package/dist/arc/obligations.js +38 -0
- package/dist/heart/awaiting/await-alert.js +4 -2
- package/dist/heart/awaiting/await-expiry.js +39 -4
- package/dist/heart/awaiting/await-parser.js +8 -0
- package/dist/heart/core.js +37 -7
- package/dist/heart/cross-chat-delivery.js +1 -0
- package/dist/heart/daemon/container-spec-auditor-main.js +11 -7
- package/dist/heart/daemon/container-spec-auditor.js +38 -11
- package/dist/heart/daemon/daemon-entry.js +19 -5
- package/dist/heart/daemon/daemon.js +154 -22
- package/dist/heart/daemon/sanctuary-acceptance-adapter.js +126 -42
- package/dist/heart/daemon/sanctuary-acceptance-harness.js +26 -29
- package/dist/heart/daemon/sanctuary-acceptance-scenarios.js +42 -51
- package/dist/heart/daemon/sanctuary-bundle-migration.js +398 -0
- package/dist/heart/external-events/router.js +1025 -43
- package/dist/heart/session-events.js +23 -5
- package/dist/heart/steward-policy.js +302 -0
- package/dist/heart/tool-approval.js +3 -1
- package/dist/heart/turn-context.js +1 -0
- package/dist/mind/context.js +8 -3
- package/dist/mind/pending.js +25 -0
- package/dist/mind/prompt.js +49 -1
- package/dist/repertoire/guardrails.js +2 -1
- package/dist/repertoire/relationship-authorization.js +194 -0
- package/dist/repertoire/tools-awaiting.js +229 -26
- package/dist/repertoire/tools-base.js +15 -0
- package/dist/repertoire/tools-continuity.js +259 -4
- package/dist/repertoire/tools-notes.js +29 -3
- package/dist/repertoire/tools-session.js +219 -4
- package/dist/repertoire/tools-steward-policy.js +88 -0
- package/dist/repertoire/tools-telegram-contacts.js +37 -0
- package/dist/repertoire/tools-unraid.js +113 -6
- package/dist/repertoire/tools.js +104 -9
- package/dist/repertoire/unraid-restart.js +86 -4
- package/dist/senses/private-runtime-worker.js +36 -3
- package/dist/senses/private-runtime.js +310 -63
- package/dist/senses/sanctuary-health-acceptance-probe.js +8 -6
- package/dist/senses/sanctuary-health-runner.js +47 -103
- package/dist/senses/sanctuary-health.js +53 -105
- package/dist/senses/sanctuary-runtime.js +36 -2
- package/dist/senses/sanctuary-sab.js +80 -0
- package/dist/senses/shared-turn.js +64 -9
- package/dist/senses/telegram-admission.js +684 -0
- package/dist/senses/telegram-approval-runtime.js +46 -6
- package/dist/senses/telegram-client.js +212 -66
- package/dist/senses/telegram-effect-adapter.js +787 -0
- package/dist/senses/telegram.js +728 -48
- package/npm-shrinkwrap.json +6 -6
- package/package.json +2 -2
|
@@ -0,0 +1,194 @@
|
|
|
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.renderRelationshipPreferences = renderRelationshipPreferences;
|
|
37
|
+
exports.withRelationshipPreference = withRelationshipPreference;
|
|
38
|
+
exports.loadRelationshipCapabilityRegistry = loadRelationshipCapabilityRegistry;
|
|
39
|
+
exports.relationshipSubjectFromFriend = relationshipSubjectFromFriend;
|
|
40
|
+
exports.createRelationshipAuthorizationEvaluator = createRelationshipAuthorizationEvaluator;
|
|
41
|
+
exports.resolveProfileScopedRelationshipAuthorization = resolveProfileScopedRelationshipAuthorization;
|
|
42
|
+
exports.authorizeRelationshipAccess = authorizeRelationshipAccess;
|
|
43
|
+
const fs = __importStar(require("node:fs"));
|
|
44
|
+
const path = __importStar(require("node:path"));
|
|
45
|
+
const node_crypto_1 = require("node:crypto");
|
|
46
|
+
const runtime_1 = require("../nerves/runtime");
|
|
47
|
+
function renderRelationshipPreferences(friend) {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
return Object.entries(friend.relationshipPolicy?.preferences ?? {})
|
|
50
|
+
.filter(([, preference]) => !preference.expiresAt || Date.parse(preference.expiresAt) > now)
|
|
51
|
+
.slice(0, 20)
|
|
52
|
+
.map(([category, preference]) => `- source=${preference.source}; provenance=${preference.provenance}; category=${category}; value=${String(preference.value).replace(/\s+/gu, " ").trim().slice(0, 500)}; version=${preference.version}; precedence=current${preference.expiresAt ? `; expires=${preference.expiresAt}` : ""}`);
|
|
53
|
+
}
|
|
54
|
+
function withRelationshipPreference(friend, input) {
|
|
55
|
+
const current = friend.relationshipPolicy ?? { schemaVersion: 1, version: 0, preferences: {} };
|
|
56
|
+
const previous = current.preferences[input.category];
|
|
57
|
+
return {
|
|
58
|
+
...friend,
|
|
59
|
+
relationshipPolicy: {
|
|
60
|
+
schemaVersion: 1,
|
|
61
|
+
version: current.version + 1,
|
|
62
|
+
preferences: { ...current.preferences, [input.category]: { value: input.value, provenance: input.provenance, source: input.source, version: (previous?.version ?? 0) + 1 } },
|
|
63
|
+
},
|
|
64
|
+
updatedAt: new Date().toISOString(),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const deny = (reason) => ({ allowed: false, reason });
|
|
68
|
+
function authorizationReceipt(value) {
|
|
69
|
+
return `relationship-${(0, node_crypto_1.createHash)("sha256").update(JSON.stringify(value)).digest("hex")}`;
|
|
70
|
+
}
|
|
71
|
+
function stringList(value, label) {
|
|
72
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || !entry.trim()))
|
|
73
|
+
throw new Error(`${label} must be a string array`);
|
|
74
|
+
return [...new Set(value)];
|
|
75
|
+
}
|
|
76
|
+
function loadRelationshipCapabilityRegistry(agentRoot) {
|
|
77
|
+
const filePath = path.join(agentRoot, "tool-profiles.json");
|
|
78
|
+
const raw = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
79
|
+
if (raw.version !== 2 || !raw.profiles || typeof raw.profiles !== "object" || Array.isArray(raw.profiles))
|
|
80
|
+
throw new Error("relationship capability registry version 2 is required");
|
|
81
|
+
const profiles = {};
|
|
82
|
+
for (const [id, value] of Object.entries(raw.profiles)) {
|
|
83
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
84
|
+
throw new Error(`relationship capability profile ${id} is invalid`);
|
|
85
|
+
const candidate = value;
|
|
86
|
+
if (!Number.isInteger(candidate.version) || Number(candidate.version) < 1)
|
|
87
|
+
throw new Error(`relationship capability profile ${id} version is invalid`);
|
|
88
|
+
profiles[id] = {
|
|
89
|
+
id,
|
|
90
|
+
version: Number(candidate.version),
|
|
91
|
+
contextScopes: stringList(candidate.contextScopes, `${id}.contextScopes`),
|
|
92
|
+
toolNames: stringList(candidate.toolNames, `${id}.toolNames`),
|
|
93
|
+
effectScopes: stringList(candidate.effectScopes, `${id}.effectScopes`),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return { version: 2, profiles };
|
|
97
|
+
}
|
|
98
|
+
function relationshipSubjectFromFriend(friend) {
|
|
99
|
+
return {
|
|
100
|
+
friendId: friend.id,
|
|
101
|
+
trustLevel: friend.trustLevel ?? "stranger",
|
|
102
|
+
admissionState: friend.admissionState ?? "unverified",
|
|
103
|
+
initiativePolicy: friend.initiativePolicy ?? "none",
|
|
104
|
+
...(friend.capabilityProfileId ? { capabilityProfileId: friend.capabilityProfileId } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function createRelationshipAuthorizationEvaluator(input) {
|
|
108
|
+
const subject = relationshipSubjectFromFriend(input.friend);
|
|
109
|
+
const relationshipProfile = subject.capabilityProfileId ? input.registry.profiles[subject.capabilityProfileId] : undefined;
|
|
110
|
+
const turnProfile = input.profileId ? input.registry.profiles[input.profileId] : relationshipProfile;
|
|
111
|
+
const profile = relationshipProfile && turnProfile
|
|
112
|
+
? {
|
|
113
|
+
id: turnProfile.id,
|
|
114
|
+
version: turnProfile.version,
|
|
115
|
+
contextScopes: turnProfile.contextScopes.filter((scope) => relationshipProfile.contextScopes.includes(scope)),
|
|
116
|
+
toolNames: turnProfile.toolNames.filter((name) => relationshipProfile.toolNames.includes(name)),
|
|
117
|
+
effectScopes: turnProfile.effectScopes.filter((scope) => relationshipProfile.effectScopes.includes(scope)),
|
|
118
|
+
}
|
|
119
|
+
: undefined;
|
|
120
|
+
const effectiveSubject = { ...subject, ...(profile ? { capabilityProfileId: profile.id } : {}) };
|
|
121
|
+
const profiles = profile ? [profile] : [];
|
|
122
|
+
const evaluate = (request) => authorizeRelationshipAccess({
|
|
123
|
+
relationship: effectiveSubject,
|
|
124
|
+
profiles,
|
|
125
|
+
request,
|
|
126
|
+
activeRequestId: input.requestId,
|
|
127
|
+
requestPhase: input.requestPhase,
|
|
128
|
+
});
|
|
129
|
+
const advertisedToolNames = profile?.toolNames.filter((name) => evaluate({ kind: "tool", name, ...(input.requestId ? { requestId: input.requestId, returnTargetFriendId: subject.friendId } : {}) }).allowed) ?? [];
|
|
130
|
+
const authorizedContextScopes = profile?.contextScopes.filter((scope) => evaluate({ kind: "context", scope }).allowed) ?? [];
|
|
131
|
+
return {
|
|
132
|
+
subject,
|
|
133
|
+
...(profile ? { profileId: profile.id } : {}),
|
|
134
|
+
authorizedContextScopes,
|
|
135
|
+
advertisedToolNames,
|
|
136
|
+
...(input.sessionEventId ? { actor: { friendId: subject.friendId, trustLevel: subject.trustLevel, sessionEventId: input.sessionEventId } } : {}),
|
|
137
|
+
authorizeContext: (scope) => evaluate({ kind: "context", scope }),
|
|
138
|
+
authorizeTool: (name) => evaluate({ kind: "tool", name, ...(input.requestId ? { requestId: input.requestId, returnTargetFriendId: subject.friendId } : {}) }),
|
|
139
|
+
authorizeEffect: (scope) => evaluate({ kind: "effect", scope, ...(input.requestId ? { requestId: input.requestId, returnTargetFriendId: subject.friendId } : {}) }),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async function resolveProfileScopedRelationshipAuthorization(input) {
|
|
143
|
+
const friends = await input.store.listAll?.() ?? [];
|
|
144
|
+
const matches = friends.filter((friend) => friend.capabilityProfileId === input.relationshipProfileId);
|
|
145
|
+
if (matches.length !== 1)
|
|
146
|
+
throw new Error(`relationship profile ${input.relationshipProfileId} must resolve to exactly one Friend`);
|
|
147
|
+
return createRelationshipAuthorizationEvaluator({
|
|
148
|
+
friend: matches[0],
|
|
149
|
+
registry: input.registry,
|
|
150
|
+
...(input.profileId ? { profileId: input.profileId } : {}),
|
|
151
|
+
...(input.requestId ? { requestId: input.requestId } : {}),
|
|
152
|
+
...(input.requestPhase ? { requestPhase: input.requestPhase } : {}),
|
|
153
|
+
...(input.sessionEventId ? { sessionEventId: input.sessionEventId } : {}),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function authorizeRelationshipAccess(input) {
|
|
157
|
+
if (input.request.kind === "admission_gate") {
|
|
158
|
+
const pending = input.pendingAdmission;
|
|
159
|
+
const now = Date.parse(input.now ?? new Date().toISOString());
|
|
160
|
+
if (!pending || pending.admissionId !== input.request.admissionId || pending.botId !== input.request.botId || pending.userId !== input.request.userId || pending.chatId !== input.request.chatId || pending.expiresAt !== input.request.expiresAt || now >= Date.parse(input.request.expiresAt) || input.request.idempotencyKey !== `ack:${input.request.admissionId}`) {
|
|
161
|
+
return deny("admission gate authorization does not match a current pending admission");
|
|
162
|
+
}
|
|
163
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.relationship_authorized", message: "authorized fixed admission gate acknowledgement", meta: { authorizationKind: "admission_gate", admissionId: input.request.admissionId } });
|
|
164
|
+
return { allowed: true, authorizationKind: "admission_gate", receiptId: authorizationReceipt(input.request), admissionId: input.request.admissionId, idempotencyKey: input.request.idempotencyKey, expiresAt: input.request.expiresAt };
|
|
165
|
+
}
|
|
166
|
+
const relationship = input.relationship;
|
|
167
|
+
if (!relationship || relationship.admissionState !== "active")
|
|
168
|
+
return deny("relationship admission is not active");
|
|
169
|
+
if (relationship.trustLevel !== "friend" && relationship.trustLevel !== "family")
|
|
170
|
+
return deny("relationship trust is insufficient");
|
|
171
|
+
const profile = input.profiles.find((candidate) => candidate.id === relationship.capabilityProfileId);
|
|
172
|
+
if (!profile || !Number.isInteger(profile.version) || profile.version < 1)
|
|
173
|
+
return deny("relationship capability profile is missing or stale");
|
|
174
|
+
if (input.request.kind === "context" && !profile.contextScopes.includes(input.request.scope))
|
|
175
|
+
return deny("context scope is not authorized by the relationship profile");
|
|
176
|
+
if (input.request.kind === "tool" && !profile.toolNames.includes(input.request.name))
|
|
177
|
+
return deny("tool is not authorized by the relationship profile");
|
|
178
|
+
if (input.request.kind === "effect" && !profile.effectScopes.includes(input.request.scope))
|
|
179
|
+
return deny("effect is not authorized by the relationship profile");
|
|
180
|
+
const requestId = input.request.kind === "context" ? undefined : input.request.requestId;
|
|
181
|
+
const returnTarget = input.request.kind === "context" ? undefined : input.request.returnTargetFriendId;
|
|
182
|
+
if (relationship.initiativePolicy === "none")
|
|
183
|
+
return deny("initiative policy denies contact");
|
|
184
|
+
if (relationship.initiativePolicy === "reactive_only" && input.request.kind !== "context") {
|
|
185
|
+
if (input.requestPhase !== "inbound" || !requestId || requestId !== input.activeRequestId || returnTarget !== relationship.friendId)
|
|
186
|
+
return deny("initiative policy is reactive only");
|
|
187
|
+
}
|
|
188
|
+
if (relationship.initiativePolicy === "request_follow_up_only" && input.request.kind !== "context") {
|
|
189
|
+
if (!requestId || requestId !== input.activeRequestId || returnTarget !== relationship.friendId)
|
|
190
|
+
return deny("initiative policy requires the matching active request and return target");
|
|
191
|
+
}
|
|
192
|
+
(0, runtime_1.emitNervesEvent)({ component: "repertoire", event: "repertoire.relationship_authorized", message: "authorized relationship access", meta: { authorizationKind: "relationship", friendId: relationship.friendId, profileId: profile.id, profileVersion: profile.version, requestKind: input.request.kind } });
|
|
193
|
+
return { allowed: true, authorizationKind: "relationship", receiptId: authorizationReceipt({ relationship, request: input.request, activeRequestId: input.activeRequestId, requestPhase: input.requestPhase, profile }), friendId: relationship.friendId, profileId: profile.id, profileVersion: profile.version, requestId: requestId ?? null };
|
|
194
|
+
}
|
|
@@ -34,8 +34,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.awaitingToolDefinitions = void 0;
|
|
37
|
+
exports.readAwaitDefinition = readAwaitDefinition;
|
|
37
38
|
exports.setAwaitToolDeps = setAwaitToolDeps;
|
|
38
39
|
exports.resetAwaitToolDeps = resetAwaitToolDeps;
|
|
40
|
+
exports.cancelAwaitTool = cancelAwaitTool;
|
|
41
|
+
exports.cancelStaleAwait = cancelStaleAwait;
|
|
42
|
+
exports.inspectRelationshipFollowUp = inspectRelationshipFollowUp;
|
|
43
|
+
exports.hasActiveRelationshipFollowUp = hasActiveRelationshipFollowUp;
|
|
44
|
+
exports.hasActiveExternalEventAwait = hasActiveExternalEventAwait;
|
|
45
|
+
exports.inspectExternalEventAwait = inspectExternalEventAwait;
|
|
46
|
+
exports.cancelRelationshipFollowUps = cancelRelationshipFollowUps;
|
|
39
47
|
const fs = __importStar(require("fs"));
|
|
40
48
|
const path = __importStar(require("path"));
|
|
41
49
|
const identity_1 = require("../heart/identity");
|
|
@@ -44,6 +52,9 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
44
52
|
const await_parser_1 = require("../heart/awaiting/await-parser");
|
|
45
53
|
const await_alert_1 = require("../heart/awaiting/await-alert");
|
|
46
54
|
const pending_1 = require("../mind/pending");
|
|
55
|
+
const router_1 = require("../heart/external-events/router");
|
|
56
|
+
const obligations_1 = require("../arc/obligations");
|
|
57
|
+
const cadence_1 = require("../heart/daemon/cadence");
|
|
47
58
|
/**
|
|
48
59
|
* Bundle-root-relative locations.
|
|
49
60
|
* - `awaiting/<name>.md` — active awaits (status: pending)
|
|
@@ -108,7 +119,7 @@ function resolveDeliveryDeps(agentName) {
|
|
|
108
119
|
return injected.buildDeliveryDeps(agentName);
|
|
109
120
|
return defaultDeliveryDeps(agentName);
|
|
110
121
|
}
|
|
111
|
-
function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel) {
|
|
122
|
+
function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel, sessionKey, requestId) {
|
|
112
123
|
const nameError = validateName(args.name);
|
|
113
124
|
if (nameError)
|
|
114
125
|
return JSON.stringify({ error: nameError });
|
|
@@ -118,26 +129,55 @@ function fileAwait(args, agentRoot, agentName, sessionFriendId, sessionChannel)
|
|
|
118
129
|
if (!args.cadence || !args.cadence.trim()) {
|
|
119
130
|
return JSON.stringify({ error: "cadence is required" });
|
|
120
131
|
}
|
|
132
|
+
if (args.wake_at && (!Number.isFinite(Date.parse(args.wake_at)) || new Date(args.wake_at).toISOString() !== args.wake_at)) {
|
|
133
|
+
return JSON.stringify({ error: "wake_at must be a canonical ISO timestamp" });
|
|
134
|
+
}
|
|
121
135
|
const filePath = awaitFilePath(agentRoot, args.name);
|
|
122
136
|
if (fs.existsSync(filePath)) {
|
|
123
137
|
return JSON.stringify({ error: `await "${args.name}" already exists` });
|
|
124
138
|
}
|
|
125
139
|
const mode = args.mode === "quick" ? "quick" : "full";
|
|
126
|
-
const alert = args.alert ?? sessionChannel ?? null;
|
|
140
|
+
const alert = sessionChannel === "external-event" ? null : args.alert ?? sessionChannel ?? null;
|
|
127
141
|
const frontmatter = {
|
|
128
142
|
condition: (0, session_events_1.capStructuredRecordString)(args.condition.trim()),
|
|
129
143
|
cadence: (0, session_events_1.capStructuredRecordString)(args.cadence.trim()),
|
|
130
144
|
alert,
|
|
131
145
|
mode,
|
|
132
146
|
max_age: typeof args.max_age === "string" ? (0, session_events_1.capStructuredRecordString)(args.max_age) : null,
|
|
147
|
+
wake_at: typeof args.wake_at === "string" ? (0, session_events_1.capStructuredRecordString)(args.wake_at) : null,
|
|
133
148
|
status: "pending",
|
|
134
149
|
created_at: new Date().toISOString(),
|
|
135
150
|
filed_from: sessionChannel ?? "unknown",
|
|
136
151
|
filed_for_friend_id: sessionFriendId ?? null,
|
|
152
|
+
filed_from_key: sessionKey,
|
|
153
|
+
request_id: requestId,
|
|
137
154
|
};
|
|
155
|
+
let obligationId = null;
|
|
156
|
+
if (requestId && sessionFriendId && sessionChannel && sessionKey) {
|
|
157
|
+
const obligation = (0, obligations_1.createObligation)(agentRoot, {
|
|
158
|
+
origin: { friendId: sessionFriendId, channel: sessionChannel, key: sessionKey },
|
|
159
|
+
owedTo: { friendId: sessionFriendId, channel: sessionChannel, key: sessionKey },
|
|
160
|
+
requestId,
|
|
161
|
+
content: args.condition.trim(),
|
|
162
|
+
});
|
|
163
|
+
(0, obligations_1.advanceObligation)(agentRoot, obligation.id, {
|
|
164
|
+
currentSurface: { kind: "session", label: `${sessionChannel}/${sessionKey}` },
|
|
165
|
+
currentArtifact: `awaiting/${args.name}.md`,
|
|
166
|
+
nextAction: args.condition.trim(),
|
|
167
|
+
});
|
|
168
|
+
obligationId = obligation.id;
|
|
169
|
+
frontmatter.obligation_id = obligation.id;
|
|
170
|
+
}
|
|
138
171
|
const rendered = (0, await_parser_1.renderAwaitFile)(frontmatter, (0, session_events_1.capStructuredRecordString)(args.body ?? ""));
|
|
139
172
|
fs.mkdirSync(awaitingDir(agentRoot), { recursive: true });
|
|
140
|
-
|
|
173
|
+
try {
|
|
174
|
+
fs.writeFileSync(filePath, rendered, "utf-8");
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
if (obligationId)
|
|
178
|
+
(0, obligations_1.fulfillObligation)(agentRoot, obligationId);
|
|
179
|
+
throw error;
|
|
180
|
+
}
|
|
141
181
|
(0, runtime_1.emitNervesEvent)({
|
|
142
182
|
component: "repertoire",
|
|
143
183
|
event: "repertoire.await_filed",
|
|
@@ -162,10 +202,14 @@ function archiveAwait(agentRoot, name, updates) {
|
|
|
162
202
|
alert: current.alert,
|
|
163
203
|
mode: current.mode,
|
|
164
204
|
max_age: current.max_age,
|
|
205
|
+
wake_at: current.wake_at ?? null,
|
|
165
206
|
status: current.status,
|
|
166
207
|
created_at: current.created_at,
|
|
167
208
|
filed_from: current.filed_from,
|
|
168
209
|
filed_for_friend_id: current.filed_for_friend_id,
|
|
210
|
+
filed_from_key: current.filed_from_key,
|
|
211
|
+
request_id: current.request_id,
|
|
212
|
+
obligation_id: current.obligation_id,
|
|
169
213
|
...updates,
|
|
170
214
|
};
|
|
171
215
|
const cappedMerged = Object.fromEntries(Object.entries(merged).map(([key, value]) => [
|
|
@@ -181,6 +225,10 @@ function archiveAwait(agentRoot, name, updates) {
|
|
|
181
225
|
const archived = (0, await_parser_1.parseAwaitFile)(archivedContent, awaitDoneFilePath(agentRoot, name));
|
|
182
226
|
return { ok: true, file: archived };
|
|
183
227
|
}
|
|
228
|
+
function fulfillAwaitObligation(agentRoot, awaitFile) {
|
|
229
|
+
if (awaitFile.obligation_id)
|
|
230
|
+
(0, obligations_1.fulfillObligation)(agentRoot, awaitFile.obligation_id);
|
|
231
|
+
}
|
|
184
232
|
async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName) {
|
|
185
233
|
const nameError = validateName(name);
|
|
186
234
|
if (nameError)
|
|
@@ -210,7 +258,31 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
210
258
|
});
|
|
211
259
|
return JSON.stringify({ verdict: "no", recorded: true });
|
|
212
260
|
}
|
|
213
|
-
//
|
|
261
|
+
// Request-bound returns stay active through Telegram prepare/send authorization.
|
|
262
|
+
let alert = null;
|
|
263
|
+
if (existing.request_id && existing.filed_from !== "external-event") {
|
|
264
|
+
try {
|
|
265
|
+
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: existing, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
266
|
+
}
|
|
267
|
+
catch (error) {
|
|
268
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
|
|
269
|
+
}
|
|
270
|
+
if (alert?.delivery?.status !== "delivered_now") {
|
|
271
|
+
return JSON.stringify({ verdict: "yes", archived: null, alert: alert ? { attempted: alert.attempted, status: alert.delivery?.status ?? null, skipped: alert.skipped ?? null } : null, advancedExternalEvents: [] });
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const advancedEvents = existing.filed_from === "external-event" && existing.filed_from_key && existing.wake_at
|
|
275
|
+
? (() => {
|
|
276
|
+
if (!hasActiveExternalEventAwait(agentName, { recordPath: existing.filed_from_key, awaitName: name, wakeAt: existing.wake_at }))
|
|
277
|
+
throw new Error("External event await authority changed before resolution");
|
|
278
|
+
const record = (0, router_1.readExternalEventRecord)(existing.filed_from_key);
|
|
279
|
+
if (record.agent !== agentName || record.recordPath !== existing.filed_from_key || record.executionState !== "handled"
|
|
280
|
+
|| record.disposition?.awaitId !== name || record.disposition.nextWake.kind !== "at" || record.disposition.nextWake.at !== existing.wake_at) {
|
|
281
|
+
throw new Error("External event await authority changed before resolution");
|
|
282
|
+
}
|
|
283
|
+
return [(0, router_1.advanceExternalEventFromAwait)(record.recordPath, { awaitId: name, expectedVersion: record.version, expectedGeneration: record.generation })];
|
|
284
|
+
})()
|
|
285
|
+
: [];
|
|
214
286
|
const archive = archiveAwait(agentRoot, name, {
|
|
215
287
|
status: "resolved",
|
|
216
288
|
resolved_at: new Date().toISOString(),
|
|
@@ -219,36 +291,26 @@ async function resolveAwaitTool(name, verdict, observation, agentRoot, agentName
|
|
|
219
291
|
/* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
|
|
220
292
|
if (!archive.ok)
|
|
221
293
|
return JSON.stringify({ error: archive.error });
|
|
294
|
+
fulfillAwaitObligation(agentRoot, archive.file);
|
|
222
295
|
(0, runtime_1.emitNervesEvent)({
|
|
223
296
|
component: "repertoire",
|
|
224
297
|
event: "repertoire.await_resolved",
|
|
225
298
|
message: "await resolved",
|
|
226
299
|
meta: { agent: agentName, name },
|
|
227
300
|
});
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
agentName,
|
|
236
|
-
deliveryDeps: resolveDeliveryDeps(agentName),
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
catch (error) {
|
|
240
|
-
(0, runtime_1.emitNervesEvent)({
|
|
241
|
-
level: "error",
|
|
242
|
-
component: "repertoire",
|
|
243
|
-
event: "repertoire.await_alert_error",
|
|
244
|
-
message: "await alert delivery threw",
|
|
245
|
-
meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) },
|
|
246
|
-
});
|
|
301
|
+
if (!existing.request_id) {
|
|
302
|
+
try {
|
|
303
|
+
alert = await (0, await_alert_1.deliverAwaitAlert)({ awaitFile: archive.file, reason: "resolved", observation: observation.trim(), agentRoot, agentName, deliveryDeps: resolveDeliveryDeps(agentName) });
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
(0, runtime_1.emitNervesEvent)({ level: "error", component: "repertoire", event: "repertoire.await_alert_error", message: "await alert delivery threw", meta: { agent: agentName, name, error: error instanceof Error ? error.message : String(error) } });
|
|
307
|
+
}
|
|
247
308
|
}
|
|
248
309
|
return JSON.stringify({
|
|
249
310
|
verdict: "yes",
|
|
250
311
|
archived: awaitDoneFilePath(agentRoot, name),
|
|
251
312
|
alert: alert ? { attempted: alert.attempted, status: alert.delivery?.status ?? null, skipped: alert.skipped ?? null } : null,
|
|
313
|
+
advancedExternalEvents: advancedEvents.map((event) => event.recordPath),
|
|
252
314
|
});
|
|
253
315
|
}
|
|
254
316
|
function cancelAwaitTool(name, reason, agentRoot, agentName) {
|
|
@@ -273,6 +335,7 @@ function cancelAwaitTool(name, reason, agentRoot, agentName) {
|
|
|
273
335
|
/* v8 ignore next -- defensive: archiveAwait only fails on the file-disappears-mid-call race already covered by v8 ignore inside archiveAwait @preserve */
|
|
274
336
|
if (!archive.ok)
|
|
275
337
|
return JSON.stringify({ error: archive.error });
|
|
338
|
+
fulfillAwaitObligation(agentRoot, archive.file);
|
|
276
339
|
(0, runtime_1.emitNervesEvent)({
|
|
277
340
|
component: "repertoire",
|
|
278
341
|
event: "repertoire.await_canceled",
|
|
@@ -281,6 +344,105 @@ function cancelAwaitTool(name, reason, agentRoot, agentName) {
|
|
|
281
344
|
});
|
|
282
345
|
return JSON.stringify({ canceled: name, archived: awaitDoneFilePath(agentRoot, name) });
|
|
283
346
|
}
|
|
347
|
+
function exactFileIsMissing(filePath) {
|
|
348
|
+
try {
|
|
349
|
+
fs.lstatSync(filePath);
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
if (error.code === "ENOENT")
|
|
354
|
+
return true;
|
|
355
|
+
throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function cancelStaleAwait(agentRoot, agentName, name, reason) {
|
|
359
|
+
const result = JSON.parse(cancelAwaitTool(name, reason, agentRoot, agentName));
|
|
360
|
+
if (result.canceled !== name)
|
|
361
|
+
throw new Error(`Stale await could not be archived for repair: ${result.error ?? name}`);
|
|
362
|
+
}
|
|
363
|
+
function inspectRelationshipFollowUp(agentRoot, input) {
|
|
364
|
+
const awaiting = readAwaitDefinition(agentRoot, input.awaitName);
|
|
365
|
+
if (!awaiting)
|
|
366
|
+
throw new Error(`Await ${input.awaitName} could not be verified`);
|
|
367
|
+
if (!awaiting.obligation_id)
|
|
368
|
+
return { active: false, reason: "request obligation is missing" };
|
|
369
|
+
const obligationPath = path.join(agentRoot, "arc", "obligations", `${awaiting.obligation_id}.json`);
|
|
370
|
+
const obligation = (0, obligations_1.readVerifiedObligations)(agentRoot).find((candidate) => candidate.id === awaiting.obligation_id);
|
|
371
|
+
if (!obligation) {
|
|
372
|
+
if (exactFileIsMissing(obligationPath))
|
|
373
|
+
return { active: false, reason: "request obligation is missing" };
|
|
374
|
+
throw new Error(`Request obligation ${awaiting.obligation_id} exists but could not be verified`);
|
|
375
|
+
}
|
|
376
|
+
if (obligation.status === "fulfilled")
|
|
377
|
+
return { active: false, reason: "request obligation is no longer active" };
|
|
378
|
+
const matches = obligation.requestId === input.requestId
|
|
379
|
+
&& obligation.origin.friendId === input.friendId && obligation.origin.channel === input.channel && obligation.origin.key === input.key
|
|
380
|
+
&& obligation.owedTo?.friendId === input.friendId && obligation.owedTo.channel === input.channel && obligation.owedTo.key === input.key
|
|
381
|
+
&& obligation.currentArtifact === `awaiting/${input.awaitName}.md`
|
|
382
|
+
&& awaiting.status === "pending" && awaiting.obligation_id === obligation.id && awaiting.request_id === input.requestId
|
|
383
|
+
&& awaiting.filed_for_friend_id === input.friendId && awaiting.filed_from === input.channel && awaiting.filed_from_key === input.key;
|
|
384
|
+
if (!matches)
|
|
385
|
+
return { active: false, reason: "request obligation binding no longer matches" };
|
|
386
|
+
const maxAge = (0, cadence_1.parseCadenceToMs)(awaiting.max_age);
|
|
387
|
+
if (input.allowElapsed !== true && maxAge !== null && awaiting.created_at && (input.now ?? Date.now()) >= Date.parse(awaiting.created_at) + maxAge) {
|
|
388
|
+
return { active: false, reason: "request obligation await expired" };
|
|
389
|
+
}
|
|
390
|
+
return { active: true };
|
|
391
|
+
}
|
|
392
|
+
function hasActiveRelationshipFollowUp(agentRoot, input) {
|
|
393
|
+
if (input.awaitName)
|
|
394
|
+
return inspectRelationshipFollowUp(agentRoot, { ...input, awaitName: input.awaitName }).active;
|
|
395
|
+
const obligation = (0, obligations_1.readVerifiedPendingObligations)(agentRoot).find((candidate) => candidate.requestId === input.requestId
|
|
396
|
+
&& candidate.origin.friendId === input.friendId && candidate.origin.channel === input.channel && candidate.origin.key === input.key
|
|
397
|
+
&& candidate.owedTo?.friendId === input.friendId && candidate.owedTo.channel === input.channel && candidate.owedTo.key === input.key
|
|
398
|
+
&& candidate.currentArtifact?.startsWith("awaiting/") && candidate.currentArtifact.endsWith(".md")
|
|
399
|
+
&& (!input.awaitName || candidate.currentArtifact === `awaiting/${input.awaitName}.md`));
|
|
400
|
+
if (!obligation)
|
|
401
|
+
return false;
|
|
402
|
+
const name = path.basename(obligation.currentArtifact, ".md");
|
|
403
|
+
if (!VALID_NAME.test(name) || obligation.currentArtifact !== `awaiting/${name}.md`)
|
|
404
|
+
return false;
|
|
405
|
+
const awaiting = readAwaitDefinition(agentRoot, name);
|
|
406
|
+
if (!awaiting || awaiting.status !== "pending" || awaiting.obligation_id !== obligation.id || awaiting.request_id !== input.requestId
|
|
407
|
+
|| awaiting.filed_for_friend_id !== input.friendId || awaiting.filed_from !== input.channel || awaiting.filed_from_key !== input.key)
|
|
408
|
+
return false;
|
|
409
|
+
const maxAge = (0, cadence_1.parseCadenceToMs)(awaiting.max_age);
|
|
410
|
+
return input.allowElapsed === true || maxAge === null || !awaiting.created_at || (input.now ?? Date.now()) < Date.parse(awaiting.created_at) + maxAge;
|
|
411
|
+
}
|
|
412
|
+
function hasActiveExternalEventAwait(agentName, input, root = (0, router_1.getExternalEventRoot)()) {
|
|
413
|
+
return inspectExternalEventAwait(agentName, input, root).active;
|
|
414
|
+
}
|
|
415
|
+
function inspectExternalEventAwait(agentName, input, root = (0, router_1.getExternalEventRoot)()) {
|
|
416
|
+
const relative = path.relative(path.resolve(root), path.resolve(input.recordPath));
|
|
417
|
+
if (relative.startsWith("..") || path.isAbsolute(relative))
|
|
418
|
+
throw new Error("External event await record is outside the event root");
|
|
419
|
+
let record;
|
|
420
|
+
try {
|
|
421
|
+
record = (0, router_1.readExternalEventRecord)(input.recordPath);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
if (error.code === "ENOENT")
|
|
425
|
+
return { active: false, reason: "external event disposition is missing" };
|
|
426
|
+
throw error;
|
|
427
|
+
}
|
|
428
|
+
const active = record.agent === agentName && record.recordPath === input.recordPath && record.executionState === "handled"
|
|
429
|
+
&& record.disposition?.awaitId === input.awaitName && record.disposition.nextWake.kind === "at" && record.disposition.nextWake.at === input.wakeAt;
|
|
430
|
+
return active ? { active: true } : { active: false, reason: "external event disposition is no longer active" };
|
|
431
|
+
}
|
|
432
|
+
function cancelRelationshipFollowUps(agentRoot, agentName, input) {
|
|
433
|
+
const obligations = (0, obligations_1.readVerifiedPendingObligations)(agentRoot).filter((candidate) => candidate.owedTo?.friendId === input.friendId
|
|
434
|
+
&& candidate.owedTo.channel === input.channel && candidate.owedTo.key === input.key);
|
|
435
|
+
for (const obligation of obligations) {
|
|
436
|
+
const artifact = obligation.currentArtifact;
|
|
437
|
+
const name = artifact?.startsWith("awaiting/") && artifact.endsWith(".md") ? path.basename(artifact, ".md") : null;
|
|
438
|
+
if (name && VALID_NAME.test(name) && artifact === `awaiting/${name}.md` && readAwaitDefinition(agentRoot, name)) {
|
|
439
|
+
cancelAwaitTool(name, "relationship revoked", agentRoot, agentName);
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
(0, obligations_1.fulfillObligation)(agentRoot, obligation.id);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
284
446
|
exports.awaitingToolDefinitions = [
|
|
285
447
|
{
|
|
286
448
|
tool: {
|
|
@@ -297,6 +459,7 @@ exports.awaitingToolDefinitions = [
|
|
|
297
459
|
alert: { type: "string", description: "Channel to alert on (e.g. 'bluebubbles', 'teams'). Defaults to filing session's channel." },
|
|
298
460
|
mode: { type: "string", description: "'full' or 'quick'. Defaults 'full'." },
|
|
299
461
|
max_age: { type: "string", description: "Optional auto-expiry (e.g. '24h')." },
|
|
462
|
+
wake_at: { type: "string", description: "Optional exact canonical ISO time this Await supports." },
|
|
300
463
|
body: { type: "string", description: "Optional notes: why I filed this, what 'ready' looks like." },
|
|
301
464
|
},
|
|
302
465
|
required: ["name", "condition", "cadence"],
|
|
@@ -306,6 +469,10 @@ exports.awaitingToolDefinitions = [
|
|
|
306
469
|
handler: (a, ctx) => {
|
|
307
470
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
308
471
|
const agentName = (0, identity_1.getAgentName)();
|
|
472
|
+
const event = ctx?.currentExternalEvent;
|
|
473
|
+
const eventFriendId = event ? ctx?.context?.friend.id ?? null : null;
|
|
474
|
+
if (event && !eventFriendId)
|
|
475
|
+
return JSON.stringify({ error: "external event await authority has no exact owner relationship" });
|
|
309
476
|
return fileAwait({
|
|
310
477
|
name: a.name,
|
|
311
478
|
condition: a.condition,
|
|
@@ -313,8 +480,9 @@ exports.awaitingToolDefinitions = [
|
|
|
313
480
|
alert: a.alert,
|
|
314
481
|
mode: a.mode,
|
|
315
482
|
max_age: a.max_age,
|
|
483
|
+
wake_at: a.wake_at,
|
|
316
484
|
body: a.body,
|
|
317
|
-
}, agentRoot, agentName, ctx?.currentSession?.friendId ?? null, ctx?.currentSession?.channel ?? null);
|
|
485
|
+
}, agentRoot, agentName, eventFriendId ?? ctx?.currentSession?.friendId ?? null, event ? "external-event" : ctx?.currentSession?.channel ?? null, event ? event.recordPath : ctx?.currentSession?.key ?? null, event ? null : ctx?.relationshipAuthorization?.requestId ?? null);
|
|
318
486
|
},
|
|
319
487
|
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "files a durable await condition" },
|
|
320
488
|
},
|
|
@@ -335,9 +503,32 @@ exports.awaitingToolDefinitions = [
|
|
|
335
503
|
},
|
|
336
504
|
},
|
|
337
505
|
},
|
|
338
|
-
handler: async (a) => {
|
|
506
|
+
handler: async (a, ctx) => {
|
|
339
507
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
340
508
|
const agentName = (0, identity_1.getAgentName)();
|
|
509
|
+
if (ctx?.relationshipAuthorization) {
|
|
510
|
+
const session = ctx.currentSession;
|
|
511
|
+
const requestId = ctx.relationshipAuthorization.requestId;
|
|
512
|
+
const existing = readAwaitDefinition(agentRoot, String(a.name ?? ""));
|
|
513
|
+
const awaitName = String(a.name ?? "");
|
|
514
|
+
let binding = null;
|
|
515
|
+
if (session?.channel === "external-event" && existing?.wake_at) {
|
|
516
|
+
binding = inspectExternalEventAwait(agentName, { recordPath: session.key, awaitName, wakeAt: existing.wake_at });
|
|
517
|
+
}
|
|
518
|
+
else if (session && requestId) {
|
|
519
|
+
if (existing?.filed_for_friend_id !== session.friendId || existing.filed_from !== session.channel
|
|
520
|
+
|| existing.filed_from_key !== session.key || existing.request_id !== requestId) {
|
|
521
|
+
return JSON.stringify({ error: "resolve_await is limited to the current relationship request or bound external event" });
|
|
522
|
+
}
|
|
523
|
+
binding = inspectRelationshipFollowUp(agentRoot, { friendId: session.friendId, channel: session.channel, key: session.key, requestId, awaitName });
|
|
524
|
+
}
|
|
525
|
+
if (binding && !binding.active) {
|
|
526
|
+
cancelStaleAwait(agentRoot, agentName, awaitName, binding.reason);
|
|
527
|
+
return JSON.stringify({ error: binding.reason });
|
|
528
|
+
}
|
|
529
|
+
if (!binding?.active)
|
|
530
|
+
return JSON.stringify({ error: "resolve_await is limited to the current relationship request or bound external event" });
|
|
531
|
+
}
|
|
341
532
|
return resolveAwaitTool(a.name, a.verdict, a.observation, agentRoot, agentName);
|
|
342
533
|
},
|
|
343
534
|
riskProfile: {
|
|
@@ -362,9 +553,21 @@ exports.awaitingToolDefinitions = [
|
|
|
362
553
|
},
|
|
363
554
|
},
|
|
364
555
|
},
|
|
365
|
-
handler: (a) => {
|
|
556
|
+
handler: (a, ctx) => {
|
|
366
557
|
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
367
558
|
const agentName = (0, identity_1.getAgentName)();
|
|
559
|
+
if (ctx?.relationshipAuthorization) {
|
|
560
|
+
const session = ctx.currentSession;
|
|
561
|
+
const requestId = ctx.relationshipAuthorization.requestId;
|
|
562
|
+
if (!session || !requestId || !hasActiveRelationshipFollowUp(agentRoot, {
|
|
563
|
+
friendId: session.friendId,
|
|
564
|
+
channel: session.channel,
|
|
565
|
+
key: session.key,
|
|
566
|
+
requestId,
|
|
567
|
+
awaitName: String(a.name ?? ""),
|
|
568
|
+
}))
|
|
569
|
+
return JSON.stringify({ error: "cancel_await is limited to the current relationship request" });
|
|
570
|
+
}
|
|
368
571
|
return cancelAwaitTool(a.name, a.reason, agentRoot, agentName);
|
|
369
572
|
},
|
|
370
573
|
riskProfile: { mutates: "durable_state_write", risk: "high", reason: "archives a durable await condition" },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = exports.renderInnerProgressStatus = exports.speakTool = exports.restTool = exports.settleTool = exports.observeTool = exports.ponderTool = void 0;
|
|
4
|
+
exports.routineActionRequester = routineActionRequester;
|
|
4
5
|
const tools_files_1 = require("./tools-files");
|
|
5
6
|
const tools_shell_1 = require("./tools-shell");
|
|
6
7
|
const tools_notes_1 = require("./tools-notes");
|
|
@@ -28,6 +29,7 @@ const tools_runtime_1 = require("./tools-runtime");
|
|
|
28
29
|
const tools_orientation_1 = require("./tools-orientation");
|
|
29
30
|
const tools_rsvp_1 = require("./tools-rsvp");
|
|
30
31
|
const tools_habits_1 = require("./tools-habits");
|
|
32
|
+
const tools_telegram_contacts_1 = require("./tools-telegram-contacts");
|
|
31
33
|
// Re-export flow tools for consumers that import them from tools-base
|
|
32
34
|
var tools_flow_1 = require("./tools-flow");
|
|
33
35
|
Object.defineProperty(exports, "ponderTool", { enumerable: true, get: function () { return tools_flow_1.ponderTool; } });
|
|
@@ -38,6 +40,18 @@ Object.defineProperty(exports, "speakTool", { enumerable: true, get: function ()
|
|
|
38
40
|
// Re-export renderInnerProgressStatus for consumers
|
|
39
41
|
var tools_session_2 = require("./tools-session");
|
|
40
42
|
Object.defineProperty(exports, "renderInnerProgressStatus", { enumerable: true, get: function () { return tools_session_2.renderInnerProgressStatus; } });
|
|
43
|
+
function routineActionRequester(ctx) {
|
|
44
|
+
const actor = ctx?.relationshipAuthorization?.actor;
|
|
45
|
+
if (!actor)
|
|
46
|
+
return null;
|
|
47
|
+
if (actor.trustLevel === "family")
|
|
48
|
+
return { kind: "owner", friendId: actor.friendId };
|
|
49
|
+
const requestId = ctx?.relationshipAuthorization?.requestId;
|
|
50
|
+
const session = ctx?.currentSession;
|
|
51
|
+
if (actor.trustLevel !== "friend" || !requestId || !session || session.friendId !== actor.friendId)
|
|
52
|
+
return null;
|
|
53
|
+
return { kind: "household_request", friendId: actor.friendId, requestId, origin: { friendId: session.friendId, channel: session.channel, key: session.key } };
|
|
54
|
+
}
|
|
41
55
|
// Tracks which file paths have been read via read_file in this session.
|
|
42
56
|
// edit_file requires a file to be read first (must-read-first guard).
|
|
43
57
|
exports.editFileReadTracker = new Set();
|
|
@@ -71,6 +85,7 @@ exports.baseToolDefinitions = [
|
|
|
71
85
|
...tools_runtime_1.runtimeToolDefinitions,
|
|
72
86
|
...tools_rsvp_1.rsvpToolDefinitions,
|
|
73
87
|
...tools_habits_1.habitToolDefinitions,
|
|
88
|
+
tools_telegram_contacts_1.telegramContactToolDefinition,
|
|
74
89
|
];
|
|
75
90
|
// Convenience array of just the tool schemas (no handler/integration metadata).
|
|
76
91
|
// Used by consumers that need the OpenAI function-tool format.
|