@ixo/oracle-runtime-workers 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +110 -0
- package/package.json +77 -0
- package/src/attachments/classify.ts +114 -0
- package/src/attachments/content-blocks.ts +51 -0
- package/src/attachments/download.ts +235 -0
- package/src/attachments/extract.ts +241 -0
- package/src/attachments/index.ts +84 -0
- package/src/attachments/magic.ts +133 -0
- package/src/attachments/pipeline.ts +409 -0
- package/src/attachments/retention.ts +205 -0
- package/src/attachments/route.ts +41 -0
- package/src/attachments/sandbox-archive.ts +90 -0
- package/src/attachments/sanitize.ts +35 -0
- package/src/attachments/types.ts +86 -0
- package/src/attachments/view.ts +107 -0
- package/src/core/env.ts +200 -0
- package/src/core/index.ts +513 -0
- package/src/core/llm.ts +842 -0
- package/src/core/main-agent-types.ts +123 -0
- package/src/core/main-agent.ts +399 -0
- package/src/core/manifest.ts +365 -0
- package/src/core/meta-tools.ts +232 -0
- package/src/core/middlewares/byo-history-sanitizer.ts +165 -0
- package/src/core/middlewares/capability-gate.ts +76 -0
- package/src/core/middlewares/index.ts +18 -0
- package/src/core/middlewares/page-context.ts +98 -0
- package/src/core/middlewares/safety-guardrail.ts +117 -0
- package/src/core/middlewares/summarization.ts +99 -0
- package/src/core/middlewares/tool-repetition-guard.ts +125 -0
- package/src/core/middlewares/tool-validation.ts +83 -0
- package/src/core/middlewares/work-status.ts +57 -0
- package/src/core/openrouter-pricing.ts +124 -0
- package/src/core/plugin-loader.ts +192 -0
- package/src/core/plugins/index.ts +6 -0
- package/src/core/plugins/skills/index.ts +8 -0
- package/src/core/plugins/skills/skills-tools.ts +356 -0
- package/src/core/plugins/skills/skills.plugin.ts +113 -0
- package/src/core/plugins/weather/index.ts +8 -0
- package/src/core/plugins/weather/weather-client.ts +153 -0
- package/src/core/plugins/weather/weather-middleware.ts +27 -0
- package/src/core/plugins/weather/weather-sub-agent.ts +65 -0
- package/src/core/plugins/weather/weather-tools.ts +103 -0
- package/src/core/plugins/weather/weather.plugin.ts +227 -0
- package/src/core/prompt-composer.ts +469 -0
- package/src/core/registries.ts +642 -0
- package/src/core/runtime-context.ts +619 -0
- package/src/core/state.ts +108 -0
- package/src/core/sub-agent-fallback.ts +187 -0
- package/src/core/subagent-as-tool.ts +322 -0
- package/src/core/template.ts +245 -0
- package/src/core/utils.ts +246 -0
- package/src/core/wrap-plugin-tool.ts +100 -0
- package/src/do/ambient.ts +266 -0
- package/src/do/contracts.ts +562 -0
- package/src/do/debug-timers.ts +120 -0
- package/src/do/fresh-stub.ts +32 -0
- package/src/do/gateway-retry.ts +73 -0
- package/src/do/secrets-adapter.ts +32 -0
- package/src/do/size-alerts.ts +76 -0
- package/src/do/sse-stream.ts +472 -0
- package/src/do/transcript.ts +192 -0
- package/src/do/turn-body.ts +84 -0
- package/src/do/turn-metadata.ts +146 -0
- package/src/do/ucan-service.ts +339 -0
- package/src/do/user-oracle-do.ts +3035 -0
- package/src/gateway-worker.ts +82 -0
- package/src/index.ts +237 -0
- package/src/llm/byo-adapter.ts +59 -0
- package/src/llm/byo-catalog.ts +520 -0
- package/src/llm/byo-client.ts +280 -0
- package/src/llm/byo-routes.ts +255 -0
- package/src/llm/byo-service.ts +836 -0
- package/src/llm/chatgpt-oauth.ts +430 -0
- package/src/llm/provider-error.ts +283 -0
- package/src/matrix/gateway-do.ts +918 -0
- package/src/matrix/ingest.ts +209 -0
- package/src/matrix/replay-format.ts +34 -0
- package/src/matrix/reply-chain.ts +109 -0
- package/src/matrix/room-state-codec.ts +115 -0
- package/src/matrix/user-homeserver.ts +106 -0
- package/src/matrix/work-status.ts +294 -0
- package/src/memory/session-history-indexer.ts +328 -0
- package/src/owner-store/ixo-vfs-store.ts +570 -0
- package/src/owner-store/matrix-media-store.ts +136 -0
- package/src/owner-store/migrating-store.ts +185 -0
- package/src/owner-store/owner-copy-errors.ts +175 -0
- package/src/owner-store/retry.ts +45 -0
- package/src/owner-store/tus-upload.ts +333 -0
- package/src/owner-store/types.ts +0 -0
- package/src/plugin-api/define-plugin.ts +45 -0
- package/src/plugin-api/index.ts +56 -0
- package/src/plugin-api/oracle-plugin.ts +145 -0
- package/src/plugin-api/tool-helper.ts +82 -0
- package/src/plugin-api/types.ts +816 -0
- package/src/plugin-api/ucan-errors.ts +16 -0
- package/src/plugins/agui/agui-agent.ts +115 -0
- package/src/plugins/agui/agui.plugin.ts +148 -0
- package/src/plugins/agui/index.ts +2 -0
- package/src/plugins/attachments/attachments.plugin.ts +54 -0
- package/src/plugins/attachments/index.ts +6 -0
- package/src/plugins/attachments/view-attachment-tool.ts +91 -0
- package/src/plugins/base64.ts +26 -0
- package/src/plugins/composio/composio-tools.ts +365 -0
- package/src/plugins/composio/composio-ucan.ts +43 -0
- package/src/plugins/composio/composio.plugin.ts +214 -0
- package/src/plugins/composio/index.ts +15 -0
- package/src/plugins/domain-indexer/domain-indexer-agent.ts +57 -0
- package/src/plugins/domain-indexer/domain-indexer-tools.ts +253 -0
- package/src/plugins/domain-indexer/domain-indexer.plugin.ts +100 -0
- package/src/plugins/domain-indexer/index.ts +3 -0
- package/src/plugins/editor/blocknote-bridge.ts +120 -0
- package/src/plugins/editor/content-session.ts +331 -0
- package/src/plugins/editor/content-tools.ts +686 -0
- package/src/plugins/editor/document-model.ts +0 -0
- package/src/plugins/editor/editor-access-denied-tool.ts +58 -0
- package/src/plugins/editor/editor-agent.ts +34 -0
- package/src/plugins/editor/editor-config.ts +64 -0
- package/src/plugins/editor/editor-mx.ts +128 -0
- package/src/plugins/editor/editor.plugin.ts +143 -0
- package/src/plugins/editor/failures.ts +154 -0
- package/src/plugins/editor/index.ts +31 -0
- package/src/plugins/editor/jsdom-shim.ts +55 -0
- package/src/plugins/editor/prompts.ts +59 -0
- package/src/plugins/editor/prop-policy.ts +147 -0
- package/src/plugins/editor/provider.ts +386 -0
- package/src/plugins/editor/room-membership.ts +95 -0
- package/src/plugins/editor/standalone-editor-tool.ts +200 -0
- package/src/plugins/firecrawl/firecrawl-agent.ts +93 -0
- package/src/plugins/firecrawl/firecrawl-tools.ts +220 -0
- package/src/plugins/firecrawl/firecrawl.plugin.ts +105 -0
- package/src/plugins/firecrawl/index.ts +15 -0
- package/src/plugins/flows/action-metadata.ts +3505 -0
- package/src/plugins/flows/actions.ts +190 -0
- package/src/plugins/flows/edit.ts +314 -0
- package/src/plugins/flows/errors.ts +38 -0
- package/src/plugins/flows/explain.ts +43 -0
- package/src/plugins/flows/flow-doc.ts +111 -0
- package/src/plugins/flows/flows.plugin.ts +129 -0
- package/src/plugins/flows/forms.ts +330 -0
- package/src/plugins/flows/index.ts +2 -0
- package/src/plugins/flows/linkage.ts +147 -0
- package/src/plugins/flows/port-types.ts +53 -0
- package/src/plugins/flows/prompts.ts +65 -0
- package/src/plugins/flows/read.ts +392 -0
- package/src/plugins/flows/references.ts +66 -0
- package/src/plugins/flows/test-support.ts +165 -0
- package/src/plugins/flows/tools/authoring.ts +516 -0
- package/src/plugins/flows/tools/discovery.ts +119 -0
- package/src/plugins/flows/tools/forms.ts +142 -0
- package/src/plugins/flows/tools/inspect.ts +165 -0
- package/src/plugins/flows/tools/linkage.ts +117 -0
- package/src/plugins/flows/tools/settings.ts +184 -0
- package/src/plugins/flows/translator.ts +328 -0
- package/src/plugins/flows/types.ts +210 -0
- package/src/plugins/flows/ydoc-helpers.ts +543 -0
- package/src/plugins/index.ts +70 -0
- package/src/plugins/mcp-call-timeout.ts +38 -0
- package/src/plugins/mcp-tool-adapter.ts +87 -0
- package/src/plugins/memory/index.ts +17 -0
- package/src/plugins/memory/memory-tools.ts +463 -0
- package/src/plugins/memory/memory-ucan.ts +51 -0
- package/src/plugins/memory/memory.plugin.ts +122 -0
- package/src/plugins/memory/types.ts +147 -0
- package/src/plugins/portal/action-log.ts +34 -0
- package/src/plugins/portal/index.ts +2 -0
- package/src/plugins/portal/portal.plugin.ts +203 -0
- package/src/plugins/sandbox/index.ts +34 -0
- package/src/plugins/sandbox/sandbox-bridge.ts +295 -0
- package/src/plugins/sandbox/sandbox-mcp.ts +127 -0
- package/src/plugins/sandbox/sandbox-write-blob.ts +103 -0
- package/src/plugins/sandbox/sandbox.plugin.ts +599 -0
- package/src/plugins/tasks/index.ts +17 -0
- package/src/plugins/tasks/manifest.ts +81 -0
- package/src/plugins/tasks/middleware.ts +219 -0
- package/src/plugins/tasks/tasks-tools.ts +414 -0
- package/src/plugins/tasks/tasks.plugin.ts +74 -0
- package/src/plugins/ucan-failure.ts +133 -0
- package/src/plugins/user-preferences/index.ts +22 -0
- package/src/plugins/user-preferences/schema.ts +31 -0
- package/src/plugins/user-preferences/user-preferences-store.ts +172 -0
- package/src/plugins/user-preferences/user-preferences-tool.ts +131 -0
- package/src/plugins/user-preferences/user-preferences.plugin.ts +137 -0
- package/src/plugins/vfs/index.ts +40 -0
- package/src/plugins/vfs/vfs-auth.ts +56 -0
- package/src/plugins/vfs/vfs-client.ts +655 -0
- package/src/plugins/vfs/vfs-content.ts +181 -0
- package/src/plugins/vfs/vfs-errors.ts +163 -0
- package/src/plugins/vfs/vfs-sandbox-tools.ts +272 -0
- package/src/plugins/vfs/vfs-tools.ts +635 -0
- package/src/plugins/vfs/vfs.plugin.ts +201 -0
- package/src/realtime/frontend-call-registry.ts +163 -0
- package/src/realtime/realtime-endpoint.ts +619 -0
- package/src/realtime/session-socket-hub.ts +249 -0
- package/src/realtime/socket-io-codec.ts +187 -0
- package/src/secrets/jwe.ts +74 -0
- package/src/secrets/pin-cipher.ts +43 -0
- package/src/secrets/secrets-service.ts +319 -0
- package/src/shell/app.ts +564 -0
- package/src/shell/auth.ts +290 -0
- package/src/sqlite/blob-codec.ts +100 -0
- package/src/sqlite/blob-compactor.ts +146 -0
- package/src/sqlite/cache-config.ts +45 -0
- package/src/sqlite/database.ts +643 -0
- package/src/sqlite/do-vfs.ts +1485 -0
- package/src/sqlite/index.ts +61 -0
- package/src/sqlite/serialization.ts +370 -0
- package/src/sqlite/sessions-store.ts +311 -0
- package/src/sqlite/sqlite-saver.ts +919 -0
- package/src/sqlite/vacuum-policy.ts +77 -0
- package/src/sqlite/wa-sqlite-loader.ts +123 -0
- package/src/sqlite/wasm.d.ts +10 -0
- package/src/tasks/schedule.ts +174 -0
- package/src/tasks/scheduler.ts +753 -0
- package/src/tasks/spec.ts +125 -0
- package/src/tasks/store.ts +351 -0
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import type { OracleIdentity, UserContextData } from '../plugin-api/types';
|
|
2
|
+
import type { UserPreferences } from './state';
|
|
3
|
+
import { compileTemplate } from './template';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Keys of UserContextData rendered into the prompt. Single source of truth for
|
|
7
|
+
* both the section labels and the iteration order in buildContextBlock.
|
|
8
|
+
*/
|
|
9
|
+
type ContextSlot =
|
|
10
|
+
| 'identity'
|
|
11
|
+
| 'work'
|
|
12
|
+
| 'goals'
|
|
13
|
+
| 'interests'
|
|
14
|
+
| 'relationships'
|
|
15
|
+
| 'recent';
|
|
16
|
+
|
|
17
|
+
// ── Memory-context shapes ───────────────────────────────────────────────────
|
|
18
|
+
// The Node runtime reads these from `@ixo/common`'s `SearchEnhancedResponse`.
|
|
19
|
+
// The Workers runtime carries the minimal structural subset the composer
|
|
20
|
+
// actually reads, so a memory plugin can populate `state.userContext` without
|
|
21
|
+
// depending on that package.
|
|
22
|
+
|
|
23
|
+
export interface MemoryEntity {
|
|
24
|
+
name: string;
|
|
25
|
+
labels?: string[];
|
|
26
|
+
summary?: string | null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MemoryFact {
|
|
30
|
+
fact?: string | null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface MemoryEpisode {
|
|
34
|
+
content?: string | null;
|
|
35
|
+
created_at?: string | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface MemoryCommunity {
|
|
39
|
+
name: string;
|
|
40
|
+
summary?: string | null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** One memory bucket (identity / work / goals / …). */
|
|
44
|
+
export interface MemoryContextSection {
|
|
45
|
+
entities?: MemoryEntity[];
|
|
46
|
+
facts?: MemoryFact[];
|
|
47
|
+
episodes?: MemoryEpisode[];
|
|
48
|
+
communities?: MemoryCommunity[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The typed view over `state.userContext` the composer renders from. */
|
|
52
|
+
export type TypedUserContext = Partial<
|
|
53
|
+
Record<ContextSlot, MemoryContextSection | undefined>
|
|
54
|
+
>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Composer inputs. Each field maps 1:1 to a slot in the base template.
|
|
58
|
+
* Plugins populate them via their middlewares; the runtime just stitches.
|
|
59
|
+
*/
|
|
60
|
+
export interface ComposePromptInput {
|
|
61
|
+
identity: OracleIdentity;
|
|
62
|
+
/** Pre-rendered Tier-1 capability block. Empty string when no eager plugins. */
|
|
63
|
+
capabilityBlock: string;
|
|
64
|
+
/**
|
|
65
|
+
* Custom Instructions section body — author-supplied standing guidance
|
|
66
|
+
* (`config.prompt.customInstructions`) plus any operating guides contributed
|
|
67
|
+
* by loaded capabilities. Empty string renders no section.
|
|
68
|
+
*/
|
|
69
|
+
customInstructions: string;
|
|
70
|
+
/** Operational-mode block — typically a multi-line string. */
|
|
71
|
+
operationalMode: string;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the discovery meta-tools (`search_skills`, `list_capabilities`,
|
|
74
|
+
* `load_capability`) are bound this turn. Default `true`. Pass `false` when
|
|
75
|
+
* the turn runs on a closed tool surface so the Operating principles do not
|
|
76
|
+
* mandate a discovery flow the model cannot run.
|
|
77
|
+
*/
|
|
78
|
+
capabilityDiscovery?: boolean;
|
|
79
|
+
/** User preferences rendered as bullets. Empty when not set. */
|
|
80
|
+
userPreferencesContext: string;
|
|
81
|
+
/** Memory-engine context blocks. */
|
|
82
|
+
userContext: UserContextData | undefined;
|
|
83
|
+
/** Time context (timezone + current time). */
|
|
84
|
+
timeContext: string;
|
|
85
|
+
/** Currently-viewed entity DID, if any. */
|
|
86
|
+
currentEntityDid: string;
|
|
87
|
+
/** Optional override of the oracle name (e.g. user preference `agentName`). */
|
|
88
|
+
oracleNameOverride?: string;
|
|
89
|
+
/** Degraded-services notice appended after the main prompt body. */
|
|
90
|
+
degradedServicesBlock?: string;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Headers used for each populated memory-context sub-section. */
|
|
94
|
+
const CONTEXT_SECTION_LABELS: Record<ContextSlot, string> = {
|
|
95
|
+
identity: 'Personal identity',
|
|
96
|
+
work: 'Work & professional',
|
|
97
|
+
goals: 'Goals & aspirations',
|
|
98
|
+
interests: 'Interests & expertise',
|
|
99
|
+
relationships: 'Relationships & social',
|
|
100
|
+
recent: 'Recent activity',
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// ── Memory-context rendering ────────────────────────────────────────────────
|
|
104
|
+
// Guardrail caps for the block. Cross-bucket dedup does the real shrinking;
|
|
105
|
+
// these only fire on pathological inputs so one entity/section can't dominate.
|
|
106
|
+
const COMPACT_ENTITY_SUMMARY_CAP = 700;
|
|
107
|
+
const COMPACT_BLOCK_BUDGET = 2000;
|
|
108
|
+
const COMPACT_OVERFLOW_NOTE = '_(More remembered — ask me to recall.)_';
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Normalize a line for duplicate detection: lowercase, strip surrounding
|
|
112
|
+
* punctuation and collapse whitespace. Intentionally conservative — it only
|
|
113
|
+
* collapses truly-identical content, so paraphrases survive as distinct facts.
|
|
114
|
+
*/
|
|
115
|
+
function normalizeForDedup(text: string): string {
|
|
116
|
+
return text
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.replace(/[.,;:!?'"()]/g, '')
|
|
119
|
+
.replace(/\s+/g, ' ')
|
|
120
|
+
.trim();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Split a (possibly multi-line) summary into distinct lines not already seen
|
|
125
|
+
* globally.
|
|
126
|
+
*/
|
|
127
|
+
function dedupSummaryLines(text: string, seen: Set<string>): string[] {
|
|
128
|
+
const out: string[] = [];
|
|
129
|
+
for (const raw of text.split('\n')) {
|
|
130
|
+
const line = raw.trim();
|
|
131
|
+
if (!line) continue;
|
|
132
|
+
const key = normalizeForDedup(line);
|
|
133
|
+
if (!key || seen.has(key)) continue;
|
|
134
|
+
seen.add(key);
|
|
135
|
+
out.push(line);
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Truncate at a sentence/word boundary so a cap never cuts mid-word. */
|
|
141
|
+
function capAtBoundary(text: string, max: number): string {
|
|
142
|
+
if (text.length <= max) return text;
|
|
143
|
+
const slice = text.slice(0, max);
|
|
144
|
+
const boundary = Math.max(
|
|
145
|
+
slice.lastIndexOf('. '),
|
|
146
|
+
slice.lastIndexOf('\n'),
|
|
147
|
+
slice.lastIndexOf('; '),
|
|
148
|
+
slice.lastIndexOf(' '),
|
|
149
|
+
);
|
|
150
|
+
const cut = boundary > max * 0.5 ? slice.slice(0, boundary) : slice;
|
|
151
|
+
return `${cut.trimEnd()}…`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Render one memory-context bucket. Dedups entities (by name) and facts (by
|
|
156
|
+
* normalized text) against a `seen` set shared across every bucket, using the
|
|
157
|
+
* richest summary collected for each entity.
|
|
158
|
+
*/
|
|
159
|
+
function formatContextSectionCompact(
|
|
160
|
+
data: MemoryContextSection | undefined,
|
|
161
|
+
seen: Set<string>,
|
|
162
|
+
richestSummary: Map<string, string>,
|
|
163
|
+
): string | null {
|
|
164
|
+
if (!data) return null;
|
|
165
|
+
const { entities, facts, episodes, communities } = data;
|
|
166
|
+
const lines: string[] = [];
|
|
167
|
+
|
|
168
|
+
if (entities?.length) {
|
|
169
|
+
const entityLines: string[] = [];
|
|
170
|
+
for (const e of entities) {
|
|
171
|
+
const nameKey = `entity:${normalizeForDedup(e.name)}`;
|
|
172
|
+
if (seen.has(nameKey)) continue;
|
|
173
|
+
seen.add(nameKey);
|
|
174
|
+
const labels = (e.labels ?? []).filter((l) => l !== 'Entity').join('/');
|
|
175
|
+
const tag = labels ? ` (${labels})` : '';
|
|
176
|
+
const rawSummary = richestSummary.get(normalizeForDedup(e.name)) ?? '';
|
|
177
|
+
const summaryParts = rawSummary
|
|
178
|
+
? dedupSummaryLines(rawSummary, seen)
|
|
179
|
+
: [];
|
|
180
|
+
const summary = summaryParts.length
|
|
181
|
+
? capAtBoundary(summaryParts.join('; '), COMPACT_ENTITY_SUMMARY_CAP)
|
|
182
|
+
: '';
|
|
183
|
+
entityLines.push(
|
|
184
|
+
summary ? `- **${e.name}**${tag}: ${summary}` : `- **${e.name}**${tag}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (entityLines.length) lines.push('_Key entities:_', ...entityLines);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (facts?.length) {
|
|
191
|
+
const factLines: string[] = [];
|
|
192
|
+
for (const f of facts) {
|
|
193
|
+
const text = f.fact?.trim();
|
|
194
|
+
if (!text) continue;
|
|
195
|
+
const key = normalizeForDedup(text);
|
|
196
|
+
if (seen.has(key)) continue;
|
|
197
|
+
seen.add(key);
|
|
198
|
+
factLines.push(`- ${text}`);
|
|
199
|
+
}
|
|
200
|
+
if (factLines.length) {
|
|
201
|
+
if (lines.length) lines.push('');
|
|
202
|
+
lines.push('_Facts:_', ...factLines);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (episodes?.length) {
|
|
207
|
+
const epLines: string[] = [];
|
|
208
|
+
for (const ep of episodes) {
|
|
209
|
+
const content = ep.content?.trim();
|
|
210
|
+
if (!content) continue;
|
|
211
|
+
const key = normalizeForDedup(content);
|
|
212
|
+
if (seen.has(key)) continue;
|
|
213
|
+
seen.add(key);
|
|
214
|
+
const date = ep.created_at?.slice(0, 10) ?? '';
|
|
215
|
+
epLines.push(date ? `- *${date}* — ${content}` : `- ${content}`);
|
|
216
|
+
}
|
|
217
|
+
if (epLines.length) {
|
|
218
|
+
if (lines.length) lines.push('');
|
|
219
|
+
lines.push('_Episodes (raw):_', ...epLines);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (communities?.length) {
|
|
224
|
+
const commLines: string[] = [];
|
|
225
|
+
for (const c of communities) {
|
|
226
|
+
const nameKey = `community:${normalizeForDedup(c.name)}`;
|
|
227
|
+
if (seen.has(nameKey)) continue;
|
|
228
|
+
seen.add(nameKey);
|
|
229
|
+
const summary = c.summary?.trim();
|
|
230
|
+
commLines.push(
|
|
231
|
+
summary ? `- **${c.name}**: ${summary}` : `- **${c.name}**`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
if (commLines.length) {
|
|
235
|
+
if (lines.length) lines.push('');
|
|
236
|
+
lines.push('_Topic clusters:_', ...commLines);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return lines.length ? lines.join('\n') : null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Pre-scan every bucket for the richest (longest) summary per entity name, so
|
|
245
|
+
* dedup keeps the most-informative copy rather than whichever bucket rendered
|
|
246
|
+
* first.
|
|
247
|
+
*/
|
|
248
|
+
function collectRichestSummaries(typed: TypedUserContext): Map<string, string> {
|
|
249
|
+
const best = new Map<string, string>();
|
|
250
|
+
for (const key of Object.keys(CONTEXT_SECTION_LABELS) as ContextSlot[]) {
|
|
251
|
+
for (const e of typed[key]?.entities ?? []) {
|
|
252
|
+
const nameKey = normalizeForDedup(e.name);
|
|
253
|
+
const summary = e.summary?.trim() ?? '';
|
|
254
|
+
const current = best.get(nameKey);
|
|
255
|
+
if (current === undefined || summary.length > current.length) {
|
|
256
|
+
best.set(nameKey, summary);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return best;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Render the memory-context block — empty string when nothing is populated.
|
|
265
|
+
* Cross-bucket dedup + guardrail budgeting are always applied.
|
|
266
|
+
*/
|
|
267
|
+
function buildContextBlock(userContext: UserContextData | undefined): string {
|
|
268
|
+
if (!userContext) return '';
|
|
269
|
+
const typed = userContext as TypedUserContext;
|
|
270
|
+
|
|
271
|
+
const seen = new Set<string>();
|
|
272
|
+
const richestSummary = collectRichestSummaries(typed);
|
|
273
|
+
const sections: string[] = [];
|
|
274
|
+
for (const key of Object.keys(CONTEXT_SECTION_LABELS) as ContextSlot[]) {
|
|
275
|
+
const formatted = formatContextSectionCompact(
|
|
276
|
+
typed[key],
|
|
277
|
+
seen,
|
|
278
|
+
richestSummary,
|
|
279
|
+
);
|
|
280
|
+
if (formatted) {
|
|
281
|
+
sections.push(`**${CONTEXT_SECTION_LABELS[key]}**\n${formatted}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const block = sections.join('\n\n');
|
|
285
|
+
if (block.length <= COMPACT_BLOCK_BUDGET) return block;
|
|
286
|
+
return `${capAtBoundary(block, COMPACT_BLOCK_BUDGET)}\n\n${COMPACT_OVERFLOW_NOTE}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Render user preferences as a bullet list for the prompt. */
|
|
290
|
+
export function formatUserPreferences(prefs?: UserPreferences): string {
|
|
291
|
+
if (!prefs) return '';
|
|
292
|
+
const lines: string[] = [];
|
|
293
|
+
if (prefs.agentName)
|
|
294
|
+
lines.push(`- **Preferred agent name:** ${prefs.agentName}`);
|
|
295
|
+
if (prefs.language) lines.push(`- **Preferred language:** ${prefs.language}`);
|
|
296
|
+
if (prefs.tone) lines.push(`- **Tone:** ${prefs.tone}`);
|
|
297
|
+
if (prefs.formality) lines.push(`- **Formality:** ${prefs.formality}`);
|
|
298
|
+
if (prefs.customInstructions)
|
|
299
|
+
lines.push(`- **Custom instructions:** ${prefs.customInstructions}`);
|
|
300
|
+
return lines.join('\n');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Build the oracle identity preamble. When `identity.prompt.opening` is set
|
|
305
|
+
* it replaces the generated text verbatim; otherwise the composer assembles
|
|
306
|
+
* a sentence from `name`/`org`/`description`.
|
|
307
|
+
*/
|
|
308
|
+
export function buildOracleSection(input: {
|
|
309
|
+
oracleName: string;
|
|
310
|
+
orgName?: string;
|
|
311
|
+
description?: string;
|
|
312
|
+
customOpening?: string;
|
|
313
|
+
}): string {
|
|
314
|
+
const { oracleName, orgName, description, customOpening } = input;
|
|
315
|
+
if (customOpening && customOpening.trim().length > 0) {
|
|
316
|
+
return customOpening.trim();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const hasOrg = orgName && orgName.length > 0;
|
|
320
|
+
const hasDescription = description && description.length > 0;
|
|
321
|
+
|
|
322
|
+
if (hasOrg && hasDescription) {
|
|
323
|
+
return `You are ${oracleName}, an AI agent operated by ${orgName}. ${description}.`;
|
|
324
|
+
}
|
|
325
|
+
if (hasOrg) {
|
|
326
|
+
return `You are ${oracleName}, an AI agent operated by ${orgName}.`;
|
|
327
|
+
}
|
|
328
|
+
if (hasDescription) {
|
|
329
|
+
return `You are ${oracleName}. ${description}.`;
|
|
330
|
+
}
|
|
331
|
+
return `You are ${oracleName}, an AI agent built on QiForge.`;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* The discovery mandate. Rendered on every turn whose surface can actually
|
|
336
|
+
* grow — i.e. wherever the meta-tools are bound.
|
|
337
|
+
*/
|
|
338
|
+
const DISCOVERY_PRINCIPLE = `- **Search first, build second.** Before doing any non-trivial task, you MUST run discovery before producing the answer yourself. That means:
|
|
339
|
+
1. Call \`search_skills\` against the user's request whenever a packaged skill could plausibly do the job (anything that involves generating a file, document, report, page, integration, calculation, lookup, or multi-step workflow).
|
|
340
|
+
2. Call \`list_capabilities\` to scan loaded + on-demand plugins when the task could be served by a plugin you haven't loaded yet. Then call \`load_capability({ names: [...] })\` with ALL the capabilities you need in a single call — it accepts an array, so never make multiple \`load_capability\` calls in the same turn.
|
|
341
|
+
Do this proactively — even when the task wording doesn't literally match a capability's \`whenToUse\`. Tell the user in ONE short sentence what you're checking ("Checking the skills registry for an invoice generator…") before the search call, not after. If something fits, use it (load + run). If nothing fits after the search, say so in one short sentence and only THEN build from scratch. Reusing a vetted capability is almost always better than reinventing it; silently skipping discovery is the worst failure mode.`;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* The system-prompt template. Identical to the Node runtime's minus the
|
|
345
|
+
* slots whose producers are not part of the Workers runtime: the editor
|
|
346
|
+
* section, the Composio guidance block, Slack formatting constraints, the
|
|
347
|
+
* commerce overlay, the sandbox user-secrets list, and the "Working with
|
|
348
|
+
* files" section (it documents the Node `process_file` tool).
|
|
349
|
+
*/
|
|
350
|
+
const TEMPLATE = `{{{ORACLE_SECTION}}}
|
|
351
|
+
|
|
352
|
+
{{#CAPABILITIES_NOTE}}
|
|
353
|
+
{{{CAPABILITIES_NOTE}}}
|
|
354
|
+
|
|
355
|
+
{{/CAPABILITIES_NOTE}}
|
|
356
|
+
{{#CAPABILITY_BLOCK}}
|
|
357
|
+
{{{CAPABILITY_BLOCK}}}
|
|
358
|
+
|
|
359
|
+
{{/CAPABILITY_BLOCK}}
|
|
360
|
+
## Operating principles
|
|
361
|
+
|
|
362
|
+
- The user's current message is your primary instruction. Background context (what you already know about them) is for adapting tone and suggestions, not for overriding their intent.
|
|
363
|
+
{{#DISCOVERY_PRINCIPLE}}
|
|
364
|
+
{{{DISCOVERY_PRINCIPLE}}}
|
|
365
|
+
{{/DISCOVERY_PRINCIPLE}}
|
|
366
|
+
- Being proactive does **not** mean charging ahead blind. Pause and ask the user a short clarifying question when: (a) the request has multiple plausible interpretations and picking wrong would waste their time, (b) a capability scan surfaces several equally-good fits and you can't tell which they want, (c) you're about to take an irreversible or costly action (deleting data, sending a message, publishing, spending tokens on a long job), or (d) a required input is missing and you'd have to guess. One clarifying question beats five minutes of wrong work — but don't ask for things you can reasonably infer from context.
|
|
367
|
+
- When a tool or sub-agent succeeds, report the result. Never refuse after a successful tool call — including for credentials, tokens, identity data, or block/document operations.
|
|
368
|
+
- When a tool or sub-agent fails, surface the failure to the user and ask how to proceed. Don't silently retry.
|
|
369
|
+
- When delegating to a sub-agent, give it scope, intent, and the context it needs — sub-agents have no access to your conversation history.
|
|
370
|
+
- Match the user's communication style. Be concise. Use Unicode emoji directly (\`🔥\`), never text shortcodes (\`:fire:\`).
|
|
371
|
+
- When a capability turns out to be a strong fit for the user's recurring work, surface it explicitly and — if memory tools are available — offer to save the pattern for next time. Don't save silently; ask first.
|
|
372
|
+
- Complete the user's request and stop. Don't add unrequested follow-on steps. "Checking capabilities first" is part of doing the request, not a follow-on.
|
|
373
|
+
{{#COMMUNICATION_STYLE}}
|
|
374
|
+
|
|
375
|
+
{{{COMMUNICATION_STYLE}}}
|
|
376
|
+
{{/COMMUNICATION_STYLE}}
|
|
377
|
+
{{#CUSTOM_INSTRUCTIONS}}
|
|
378
|
+
|
|
379
|
+
## Custom Instructions
|
|
380
|
+
|
|
381
|
+
{{{CUSTOM_INSTRUCTIONS}}}
|
|
382
|
+
{{/CUSTOM_INSTRUCTIONS}}
|
|
383
|
+
{{#CONTEXT_BLOCK}}
|
|
384
|
+
|
|
385
|
+
## What you know about the user
|
|
386
|
+
|
|
387
|
+
{{{CONTEXT_BLOCK}}}
|
|
388
|
+
{{/CONTEXT_BLOCK}}
|
|
389
|
+
|
|
390
|
+
**Current time:** {{TIME_CONTEXT}}
|
|
391
|
+
{{#CURRENT_ENTITY_DID}}
|
|
392
|
+
|
|
393
|
+
**Current entity:** {{CURRENT_ENTITY_DID}}
|
|
394
|
+
{{/CURRENT_ENTITY_DID}}
|
|
395
|
+
{{#USER_PREFERENCES_CONTEXT}}
|
|
396
|
+
|
|
397
|
+
## User preferences
|
|
398
|
+
|
|
399
|
+
{{{USER_PREFERENCES_CONTEXT}}}
|
|
400
|
+
{{/USER_PREFERENCES_CONTEXT}}
|
|
401
|
+
|
|
402
|
+
## Operational mode
|
|
403
|
+
|
|
404
|
+
{{{OPERATIONAL_MODE}}}
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
const PROMPT_TEMPLATE = compileTemplate(TEMPLATE);
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Compose the runtime's system prompt. Slots split into:
|
|
411
|
+
* - **identity** — from `OracleConfig` (custom opening or generated).
|
|
412
|
+
* - **capabilities** — author note (config.prompt.capabilities) + Tier-1 block.
|
|
413
|
+
* - **operating principles** — fixed + optional `communicationStyle` from config.
|
|
414
|
+
* - **context** — only populated memory sub-sections render; empty ones skipped.
|
|
415
|
+
* - **operational mode** — host hook or the runtime default.
|
|
416
|
+
*
|
|
417
|
+
* Synchronous (the Node version was async only because LangChain's
|
|
418
|
+
* `PromptTemplate.format` is); kept `Promise`-returning for call-site parity.
|
|
419
|
+
*/
|
|
420
|
+
export async function composePrompt(
|
|
421
|
+
input: ComposePromptInput,
|
|
422
|
+
): Promise<string> {
|
|
423
|
+
const oracleName = input.oracleNameOverride ?? input.identity.name;
|
|
424
|
+
const oracleSection = buildOracleSection({
|
|
425
|
+
oracleName,
|
|
426
|
+
orgName: input.identity.org,
|
|
427
|
+
description: input.identity.description,
|
|
428
|
+
customOpening: input.identity.prompt?.opening,
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const capabilitiesNote = input.identity.prompt?.capabilities?.trim() ?? '';
|
|
432
|
+
const communicationStyle =
|
|
433
|
+
input.identity.prompt?.communicationStyle?.trim() ?? '';
|
|
434
|
+
|
|
435
|
+
const rendered = PROMPT_TEMPLATE.render({
|
|
436
|
+
ORACLE_SECTION: oracleSection,
|
|
437
|
+
CAPABILITIES_NOTE: capabilitiesNote,
|
|
438
|
+
CAPABILITY_BLOCK: input.capabilityBlock,
|
|
439
|
+
DISCOVERY_PRINCIPLE:
|
|
440
|
+
input.capabilityDiscovery === false ? '' : DISCOVERY_PRINCIPLE,
|
|
441
|
+
CUSTOM_INSTRUCTIONS: input.customInstructions,
|
|
442
|
+
COMMUNICATION_STYLE: communicationStyle,
|
|
443
|
+
CONTEXT_BLOCK: buildContextBlock(input.userContext),
|
|
444
|
+
TIME_CONTEXT: input.timeContext,
|
|
445
|
+
CURRENT_ENTITY_DID: input.currentEntityDid,
|
|
446
|
+
OPERATIONAL_MODE: input.operationalMode,
|
|
447
|
+
USER_PREFERENCES_CONTEXT: input.userPreferencesContext,
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
if (input.degradedServicesBlock && input.degradedServicesBlock.length > 0) {
|
|
451
|
+
return `${rendered}\n\n---\n\n## Degraded services\n\n${input.degradedServicesBlock}\n`;
|
|
452
|
+
}
|
|
453
|
+
return rendered;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Format a (timezone, currentTime) pair into a stable prompt block. Public so
|
|
458
|
+
* forks driving their own `composePrompt` calls can re-use it.
|
|
459
|
+
*/
|
|
460
|
+
export function formatTimeContext(
|
|
461
|
+
timezone: string | undefined,
|
|
462
|
+
currentTime: string | undefined,
|
|
463
|
+
): string {
|
|
464
|
+
if (!timezone && !currentTime) return 'Not available.';
|
|
465
|
+
const parts: string[] = [];
|
|
466
|
+
if (currentTime) parts.push(currentTime);
|
|
467
|
+
if (timezone) parts.push(`(${timezone})`);
|
|
468
|
+
return parts.join(' ') || 'Not available.';
|
|
469
|
+
}
|