@stackbilt/aegis-core 0.6.4 → 0.7.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.
Files changed (112) hide show
  1. package/cli/aegis.mjs +356 -0
  2. package/package.json +9 -1
  3. package/schema.sql +4 -0
  4. package/src/adapters/voice/cloudflare-agent.ts +0 -0
  5. package/src/auth.ts +19 -7
  6. package/src/bluesky.ts +0 -0
  7. package/src/claude-tools/content.ts +0 -0
  8. package/src/claude-tools/email.ts +0 -0
  9. package/src/claude.ts +133 -268
  10. package/src/codebeast.ts +0 -0
  11. package/src/composite.ts +49 -79
  12. package/src/content/column.ts +0 -0
  13. package/src/content/hero-image.ts +0 -0
  14. package/src/content/index.ts +0 -0
  15. package/src/content/journal.ts +0 -0
  16. package/src/content/roundtable.ts +0 -0
  17. package/src/contracts/agenda-item.contract.ts +0 -0
  18. package/src/contracts/cc-task.contract.ts +0 -0
  19. package/src/contracts/goal.contract.ts +0 -0
  20. package/src/contracts/memory-entry.contract.ts +0 -0
  21. package/src/core.ts +5 -0
  22. package/src/dashboard.ts +0 -0
  23. package/src/decision-docs.ts +0 -0
  24. package/src/dispatch.ts +0 -0
  25. package/src/durable-objects/chat-session-auth.ts +20 -0
  26. package/src/durable-objects/chat-session.ts +251 -0
  27. package/src/edge-env.ts +0 -0
  28. package/src/exports.ts +0 -0
  29. package/src/github-projects.ts +0 -0
  30. package/src/groq.ts +61 -113
  31. package/src/index.ts +4 -0
  32. package/src/kernel/argus-actions.ts +0 -0
  33. package/src/kernel/argus-correlation.ts +0 -0
  34. package/src/kernel/board.ts +0 -0
  35. package/src/kernel/classify-memory-topic.ts +0 -0
  36. package/src/kernel/disambiguation.ts +55 -0
  37. package/src/kernel/dispatch.ts +59 -44
  38. package/src/kernel/dynamic-tools.ts +30 -52
  39. package/src/kernel/executor-port.ts +0 -0
  40. package/src/kernel/executor-router.ts +0 -0
  41. package/src/kernel/executors/claude.ts +1 -0
  42. package/src/kernel/executors/direct.ts +14 -0
  43. package/src/kernel/executors/workers-ai.ts +5 -0
  44. package/src/kernel/grounding/fabrication-detector.ts +0 -0
  45. package/src/kernel/grounding/fanout.ts +0 -0
  46. package/src/kernel/grounding/semantic-sanhedrin.ts +0 -0
  47. package/src/kernel/grounding/verify.ts +0 -0
  48. package/src/kernel/grounding-layer.ts +0 -0
  49. package/src/kernel/insight-cache.ts +0 -0
  50. package/src/kernel/memory/episodic.ts +3 -1
  51. package/src/kernel/memory/insights.ts +0 -0
  52. package/src/kernel/memory-guardrails.ts +0 -0
  53. package/src/kernel/memory-service.ts +0 -0
  54. package/src/kernel/patterns.ts +0 -0
  55. package/src/kernel/port.ts +0 -0
  56. package/src/kernel/provider-factory.ts +0 -0
  57. package/src/kernel/resilience.ts +0 -0
  58. package/src/kernel/router.ts +33 -11
  59. package/src/kernel/scheduled/agent-dispatch.ts +0 -0
  60. package/src/kernel/scheduled/argus-analytics.ts +0 -0
  61. package/src/kernel/scheduled/argus-heartbeat.ts +0 -0
  62. package/src/kernel/scheduled/argus-notify.ts +0 -0
  63. package/src/kernel/scheduled/board-sync.ts +0 -0
  64. package/src/kernel/scheduled/ci-watcher.ts +0 -0
  65. package/src/kernel/scheduled/content-drip.ts +0 -0
  66. package/src/kernel/scheduled/content.ts +0 -0
  67. package/src/kernel/scheduled/conversation-facts.ts +9 -7
  68. package/src/kernel/scheduled/cost-report.ts +0 -0
  69. package/src/kernel/scheduled/dev-activity.ts +0 -0
  70. package/src/kernel/scheduled/digest.ts +30 -3
  71. package/src/kernel/scheduled/dreaming/agenda-triage.ts +0 -0
  72. package/src/kernel/scheduled/dreaming/facts.ts +0 -0
  73. package/src/kernel/scheduled/dreaming/index.ts +0 -0
  74. package/src/kernel/scheduled/dreaming/llm.ts +9 -5
  75. package/src/kernel/scheduled/dreaming/pattern-synthesis.ts +0 -0
  76. package/src/kernel/scheduled/dreaming/persona.ts +0 -0
  77. package/src/kernel/scheduled/dreaming/symbolic.ts +0 -0
  78. package/src/kernel/scheduled/dreaming/task-proposals.ts +0 -0
  79. package/src/kernel/scheduled/entropy.ts +0 -0
  80. package/src/kernel/scheduled/feed-watcher.ts +0 -0
  81. package/src/kernel/scheduled/inbox-processor.ts +0 -0
  82. package/src/kernel/scheduled/issue-proposer.ts +0 -0
  83. package/src/kernel/scheduled/issue-watcher.ts +0 -0
  84. package/src/kernel/scheduled/pr-automerge.ts +0 -0
  85. package/src/kernel/scheduled/product-health.ts +0 -0
  86. package/src/kernel/scheduled/self-improvement.ts +0 -0
  87. package/src/kernel/scheduled/social-engage.ts +12 -8
  88. package/src/kernel/scheduled/task-audit.ts +0 -0
  89. package/src/kernel/types.ts +6 -0
  90. package/src/landing.ts +0 -0
  91. package/src/lib/audit-chain/chain.ts +0 -0
  92. package/src/lib/audit-chain/types.ts +0 -0
  93. package/src/lib/observability/errors.ts +0 -0
  94. package/src/operator/config.ts +0 -0
  95. package/src/operator/persona.ts +0 -0
  96. package/src/operator/prompt-builder.ts +3 -0
  97. package/src/pulse.ts +0 -0
  98. package/src/routes/bluesky.ts +0 -0
  99. package/src/routes/chat-ws.ts +17 -0
  100. package/src/routes/codebeast.ts +0 -0
  101. package/src/routes/content.ts +0 -0
  102. package/src/routes/dynamic-tools.ts +0 -0
  103. package/src/routes/observability.ts +0 -0
  104. package/src/routes/operator-logs.ts +0 -0
  105. package/src/routes/pages.ts +5 -1
  106. package/src/schema-enums.ts +0 -0
  107. package/src/task-intelligence.ts +0 -0
  108. package/src/types.ts +6 -0
  109. package/src/ui.ts +594 -2
  110. package/src/version.ts +3 -3
  111. package/src/wiki/client.ts +0 -0
  112. package/src/wiki/types.ts +0 -0
@@ -53,7 +53,11 @@ export async function executeGptOss(
53
53
  githubToken: env.githubToken,
54
54
  githubRepo: env.githubRepo,
55
55
  braveApiKey: env.braveApiKey,
56
+ roundtableDb: env.roundtableDb,
57
+ memoryBinding: env.memoryBinding,
58
+ resendApiKeys: { resendApiKey: env.resendApiKey, resendApiKeyPersonal: env.resendApiKeyPersonal },
56
59
  userQuery: intent.raw,
60
+ edgeEnv: env,
57
61
  };
58
62
  const { systemPrompt, tools: anthropicTools } = await buildContext(pseudoConfig);
59
63
  // toOpenAiTools output matches factory Tool shape exactly
@@ -105,6 +109,7 @@ export async function executeGptOss(
105
109
  env.githubToken, env.githubRepo, env.braveApiKey,
106
110
  undefined, undefined, env.memoryBinding,
107
111
  { resendApiKey: env.resendApiKey, resendApiKeyPersonal: env.resendApiKeyPersonal },
112
+ env,
108
113
  );
109
114
 
110
115
  if (inProcess !== null) {
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -16,13 +16,15 @@ export function sanitizeEpisodicOutcome(raw: string | null | undefined): Episodi
16
16
 
17
17
  export async function recordEpisode(db: D1Database, entry: Omit<EpisodicEntry, 'id' | 'created_at'>): Promise<void> {
18
18
  const safeOutcome = sanitizeEpisodicOutcome(entry.outcome);
19
+ const groundingGap = entry.grounding_gap ? 1 : 0;
19
20
  await db.prepare(
20
- 'INSERT INTO episodic_memory (intent_class, channel, summary, outcome, cost, latency_ms, near_miss, classifier_confidence, reclassified, thread_id, executor) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
21
+ 'INSERT INTO episodic_memory (intent_class, channel, summary, outcome, cost, latency_ms, near_miss, classifier_confidence, reclassified, thread_id, executor, grounding_gap) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
21
22
  ).bind(
22
23
  entry.intent_class, entry.channel, entry.summary, safeOutcome,
23
24
  entry.cost, entry.latency_ms, entry.near_miss ?? null,
24
25
  entry.classifier_confidence ?? null, entry.reclassified ? 1 : 0,
25
26
  entry.thread_id ?? null, entry.executor ?? null,
27
+ groundingGap,
26
28
  ).run();
27
29
  }
28
30
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,8 +1,10 @@
1
+ import { createLLMProviderFactory } from '@stackbilt/llm-providers';
1
2
  import { getProcedure, findNearMiss, procedureKey, PROCEDURE_MIN_SUCCESSES, PROCEDURE_MIN_SUCCESS_RATE, getConversationHistory } from './memory/index.js';
2
3
  import { askGroq, askGroqWithLogprobs } from '../groq.js';
3
4
  import type { KernelIntent, ExecutionPlan, Executor } from './types.js';
4
5
  import { buildClassifySystem, getTaskPatterns } from '../operator/prompt-builder.js';
5
6
  import { domainPreFilter } from './domain.js';
7
+ import { detectDisambiguationNeed } from './disambiguation.js';
6
8
 
7
9
  // ─── Confidence Thresholds ──────────────────────────────────
8
10
  const CONFIDENCE_TRUST = 0.80; // ≥ 0.80 → use classification as-is
@@ -72,21 +74,21 @@ async function classifyWithWorkersAI(
72
74
  systemPrompt: string,
73
75
  userPrompt: string,
74
76
  ): Promise<string> {
75
- const result = await ai.run('@cf/meta/llama-3.2-3b-instruct', {
77
+ const result = await createLLMProviderFactory({
78
+ cloudflare: { ai },
79
+ fallbackRules: [],
80
+ enableCircuitBreaker: true,
81
+ enableRetries: true,
82
+ }).generateResponse({
83
+ model: '@cf/meta/llama-3.1-8b-instruct',
84
+ systemPrompt,
76
85
  messages: [
77
- { role: 'system', content: systemPrompt },
78
86
  { role: 'user', content: userPrompt },
79
87
  ],
80
- max_tokens: 200,
88
+ maxTokens: 200,
81
89
  temperature: 0.1,
82
- }) as { response?: unknown };
83
- const raw = result.response;
84
- if (typeof raw === 'string') return raw;
85
- if (raw == null) return '';
86
- // Workers AI sometimes returns structured responses (objects with tool_calls,
87
- // arrays of segments, etc.). Coerce to string so downstream .trim()/JSON.parse
88
- // callers don't crash on non-string payloads.
89
- return typeof raw === 'object' ? JSON.stringify(raw) : String(raw);
90
+ });
91
+ return result.message ?? '';
90
92
  }
91
93
 
92
94
 
@@ -94,6 +96,7 @@ async function classifyWithWorkersAI(
94
96
  // Fallback routes — used for degraded procedure replanning and when JSON classification fails
95
97
  const DEFAULT_ROUTES: Record<string, Executor> = {
96
98
  heartbeat: 'direct',
99
+ request_clarification: 'direct',
97
100
  bizops_read: 'gpt_oss',
98
101
  bizops_mutate: 'gpt_oss',
99
102
  general_knowledge: 'gpt_oss',
@@ -241,6 +244,25 @@ export async function route(
241
244
  };
242
245
  }
243
246
 
247
+ // ─── Phase 0.25: Disambiguation firewall ──────────────────
248
+ const disambiguation = detectDisambiguationNeed(intent.raw);
249
+ if (disambiguation) {
250
+ intent.classified = 'request_clarification';
251
+ intent.complexity = 0;
252
+ intent.needsTools = false;
253
+ intent.confidence = 1;
254
+ intent.disambiguation = disambiguation;
255
+ return {
256
+ plan: {
257
+ executor: 'direct',
258
+ reasoning: `Disambiguation firewall halted undefined data concept "${disambiguation.concept}"`,
259
+ costCeiling: 'free',
260
+ },
261
+ nearMiss: `disambiguation:${disambiguation.concept}`,
262
+ reclassified: false,
263
+ };
264
+ }
265
+
244
266
  // ─── Phase 0.5: Domain pre-filter (observe only) ──────────
245
267
  const domainTag = domainPreFilter(intent.raw);
246
268
  intent.domain = domainTag.domain;
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -5,6 +5,7 @@
5
5
  // Workers AI llama-3.1-8b (free tier) as fallback.
6
6
 
7
7
  import { type EdgeEnv } from '../dispatch.js';
8
+ import { buildLLMProviderFactory } from '../provider-factory.js';
8
9
  import { recordMemory as recordMemoryAdapter } from '../memory-adapter.js';
9
10
  import { askGroq } from '../../groq.js';
10
11
  import { pushFactsToMindSpring, type FactEntry } from './mindspring-notebook.js';
@@ -97,13 +98,14 @@ async function askAi(
97
98
  }
98
99
  // Workers AI fallback — llama-3.1-8b is on the genuine free tier
99
100
  if (env.ai) {
100
- const result = await env.ai.run(
101
- '@cf/meta/llama-3.1-8b-instruct' as Parameters<Ai['run']>[0],
102
- { messages: [{ role: 'system', content: system }, { role: 'user', content: user }] },
103
- );
104
- if (typeof result === 'string') return result;
105
- const obj = result as { response?: string; choices?: Array<{ message?: { content?: string } }> };
106
- return obj.choices?.[0]?.message?.content ?? obj.response ?? '';
101
+ const result = await buildLLMProviderFactory(env).generateResponse({
102
+ model: '@cf/meta/llama-3.1-8b-instruct',
103
+ systemPrompt: system,
104
+ messages: [{ role: 'user', content: user }],
105
+ maxTokens: 1024,
106
+ temperature: 0.2,
107
+ });
108
+ return result.message ?? '';
107
109
  }
108
110
  throw new Error('[conv-facts] No LLM provider available (groqApiKey and env.ai both missing)');
109
111
  }
File without changes
File without changes
@@ -17,9 +17,11 @@ import {
17
17
  // Operator log runs at 08:00 UTC to generate content before this fires.
18
18
 
19
19
  export async function runDailyDigest(env: EdgeEnv): Promise<void> {
20
- // Time gate: fire at 09:00 UTC (4:00 AM CT)
20
+ // Time gate: fire at or after 09:00 UTC (4:00 AM CT).
21
+ // The 22h cooldown below prevents double-sends, while allowing same-day
22
+ // catch-up after a transient 09:00 send failure.
21
23
  const now = new Date();
22
- if (now.getUTCHours() !== 9) return;
24
+ if (now.getUTCHours() < 9) return;
23
25
 
24
26
  // Cooldown: 22 hours since last digest
25
27
  const lastDigest = await env.db.prepare(
@@ -253,7 +255,7 @@ export async function runDailyDigest(env: EdgeEnv): Promise<void> {
253
255
  }
254
256
 
255
257
  // ── Send digest email ──
256
- await sendDailyDigest(
258
+ await sendDailyDigestWithRetry(
257
259
  { resendApiKey: env.resendApiKey, resendApiKeyPersonal: env.resendApiKeyPersonal },
258
260
  sections,
259
261
  env.notifyEmail,
@@ -272,6 +274,31 @@ export async function runDailyDigest(env: EdgeEnv): Promise<void> {
272
274
  console.log(`[digest] Daily digest sent: ${sections.completedTasks.length} completed, ${sections.failedTasks.length} failed, ${sections.proposedTasks.length} proposed, ${sections.healthChecks.length} health checks`);
273
275
  }
274
276
 
277
+ export async function sendDailyDigestWithRetry(
278
+ apiKeys: { resendApiKey: string; resendApiKeyPersonal: string },
279
+ sections: DigestSections,
280
+ notifyEmail?: string,
281
+ delaysMs: number[] = [1_000, 5_000],
282
+ ): Promise<void> {
283
+ let attempt = 0;
284
+ for (;;) {
285
+ try {
286
+ await sendDailyDigest(apiKeys, sections, notifyEmail);
287
+ return;
288
+ } catch (err) {
289
+ if (attempt >= delaysMs.length) throw err;
290
+ const delayMs = delaysMs[attempt];
291
+ attempt += 1;
292
+ console.warn(
293
+ `[digest] Daily digest send failed; retrying in ${delayMs}ms ` +
294
+ `(attempt ${attempt + 1}/${delaysMs.length + 1}):`,
295
+ err instanceof Error ? err.message : String(err),
296
+ );
297
+ await new Promise(resolve => setTimeout(resolve, delayMs));
298
+ }
299
+ }
300
+ }
301
+
275
302
  // ─── Health Check Deduplication (#309) ──────────────────────
276
303
  // Each DigestHealthCheck has { severity, checks: [{name, status, detail}], timestamp }.
277
304
  // Multiple heartbeat runs can produce entries with the same check name (e.g. stale_agenda_151).
File without changes
File without changes
File without changes
@@ -1,6 +1,7 @@
1
1
  // Shared LLM helper — Groq first (free, 70B quality), Workers AI 70B fallback
2
2
 
3
3
  import type { EdgeEnv } from '../../dispatch.js';
4
+ import { buildLLMProviderFactory } from '../../provider-factory.js';
4
5
  import { askGroq } from '../../../groq.js';
5
6
 
6
7
  export async function askWorkersAiOrGroq(
@@ -20,11 +21,14 @@ export async function askWorkersAiOrGroq(
20
21
  }
21
22
  // Workers AI fallback — only fires if Groq is unavailable or throws
22
23
  if (env.ai) {
23
- const result = await env.ai.run(
24
- '@cf/meta/llama-3.3-70b-instruct-fp8-fast' as Parameters<Ai['run']>[0],
25
- { messages: [{ role: 'system', content: system }, { role: 'user', content: user }] },
26
- ) as { response?: string; choices?: Array<{ message?: { content?: string } }> };
27
- return result.choices?.[0]?.message?.content ?? result.response ?? '';
24
+ const result = await buildLLMProviderFactory(env).generateResponse({
25
+ model: '@cf/meta/llama-3.3-70b-instruct-fp8-fast',
26
+ systemPrompt: system,
27
+ messages: [{ role: 'user', content: user }],
28
+ maxTokens: 2048,
29
+ temperature: 0.2,
30
+ });
31
+ return result.message ?? '';
28
32
  }
29
33
  throw new Error('[dreaming] No LLM provider available (groqApiKey and env.ai both missing)');
30
34
  }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -2,6 +2,7 @@
2
2
  // Runs every 6 hours. Likes replies, follows back real accounts,
3
3
  // replies to substantive comments with Workers AI.
4
4
 
5
+ import { createLLMProviderFactory } from '@stackbilt/llm-providers';
5
6
  import { type EdgeEnv } from '../dispatch.js';
6
7
  import {
7
8
  getNotifications,
@@ -153,21 +154,24 @@ async function generateReply(
153
154
  incomingText: string,
154
155
  authorHandle: string,
155
156
  ): Promise<string | null> {
156
- const result = await ai.run('@cf/meta/llama-3.1-8b-instruct' as Parameters<Ai['run']>[0], {
157
+ const result = await createLLMProviderFactory({
158
+ cloudflare: { ai },
159
+ fallbackRules: [],
160
+ enableCircuitBreaker: true,
161
+ enableRetries: true,
162
+ }).generateResponse({
163
+ model: '@cf/meta/llama-3.1-8b-instruct',
164
+ systemPrompt: `You are the operator's social media voice. Direct, builder-energy, anti-corporate. No emoji spam. No "excited to announce." Keep replies under 200 chars. Be genuine and conversational. If you can't add value, return SKIP.`,
157
165
  messages: [
158
- {
159
- role: 'system',
160
- content: `You are the operator's social media voice. Direct, builder-energy, anti-corporate. No emoji spam. No "excited to announce." Keep replies under 200 chars. Be genuine and conversational. If you can't add value, return SKIP.`,
161
- },
162
166
  {
163
167
  role: 'user',
164
168
  content: `@${authorHandle} replied to our Bluesky post: "${incomingText}"\n\nWrite a brief, genuine reply. Return ONLY the reply text, or SKIP if there's nothing meaningful to add.`,
165
169
  },
166
170
  ],
167
- max_tokens: 100,
168
- }) as { response?: string };
171
+ maxTokens: 100,
172
+ });
169
173
 
170
- const reply = result.response?.trim();
174
+ const reply = result.message?.trim();
171
175
  if (!reply || reply === 'SKIP' || reply.length < 5) return null;
172
176
 
173
177
  // Safety: truncate to 300 chars (Bluesky limit)
File without changes
@@ -18,9 +18,14 @@ export interface KernelIntent {
18
18
  confidence?: number;
19
19
  domain?: string;
20
20
  domainConfidence?: number;
21
+ disambiguation?: {
22
+ concept: string;
23
+ question: string;
24
+ };
21
25
  timestamp: number;
22
26
  costCeiling: 'free' | 'cheap' | 'expensive';
23
27
  classifierSource?: 'classify-cast' | 'workers-ai' | 'groq';
28
+ forcedExecutor?: Executor;
24
29
  }
25
30
 
26
31
  // ─── Memory Types ────────────────────────────────────────────
@@ -40,6 +45,7 @@ export interface EpisodicEntry {
40
45
  executor?: string | null;
41
46
  complexity_tier?: string | null;
42
47
  executor_config?: string | null;
48
+ grounding_gap?: boolean | number;
43
49
  created_at?: string;
44
50
  }
45
51
 
package/src/landing.ts CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -81,6 +81,7 @@ If conversation context is provided, use it to understand what the user is reall
81
81
 
82
82
  Categories:
83
83
  - heartbeat: Explicitly asking to run a health check, status check, or heartbeat — ONLY when the user directly requests a system diagnostic. NOT for pasting technical data, reporting findings, discussing system state, or asking what needs attention
84
+ - request_clarification: The user asks for an internal data or metric concept whose definition is not strictly specified. Halt and ask which definition to use instead of guessing
84
85
  ${bizopsCategories}
85
86
  - general_knowledge: General factual questions, conceptual explanations, abstract advice with NO connection to the operator's businesses, projects, or operations
86
87
  - memory_recall: Questions about what the agent remembers, past conversations
@@ -100,6 +101,7 @@ Tiebreaker: "roundtable", "generate a roundtable", "show roundtable drafts", "pu
100
101
  Tiebreaker: When the user discusses actions to take on their business infrastructure, projects, migrations, deployments, or asks for recommendations tied to business operations → prefer bizops_mutate over general_knowledge. "general_knowledge" is for questions with NO business/operational context (e.g., "what is OAuth?", "explain quantum computing").
101
102
  Tiebreaker: When a message reports a bug, error, broken feature, or asks for help using a Stackbilt product → always support_triage, never bizops_read. "bizops_read" is for the operator querying internal business state, not for end-user support requests.
102
103
  Tiebreaker: If the user asks "what do you think?" or "what are your thoughts?" in a conversation about business decisions, projects, or operations → bizops_mutate (they want actionable advice + BizOps actions, not a generic essay).
104
+ Disambiguation firewall: If the user asks for data or metrics and the definition is not strictly defined in context, DO NOT GUESS. Use request_clarification. Example: "what is churn?" is ambiguous; ask whether they mean churn by seat count, account count, revenue, MRR, or another defined basis.
103
105
 
104
106
  Examples:
105
107
  - "hi" → {"pattern":"greeting","complexity":0,"needs_tools":false,"confidence":0.99}
@@ -167,6 +169,7 @@ export function getTaskPatterns(): readonly string[] {
167
169
 
168
170
  const patterns: string[] = [
169
171
  'heartbeat',
172
+ 'request_clarification',
170
173
  'general_knowledge',
171
174
  'memory_recall',
172
175
  'greeting',
package/src/pulse.ts CHANGED
File without changes
File without changes
@@ -0,0 +1,17 @@
1
+ import { Hono } from 'hono';
2
+ import type { Env } from '../types.js';
3
+
4
+ export const chatWs = new Hono<{ Bindings: Env }>();
5
+
6
+ chatWs.get('/chat/ws', async (c) => {
7
+ if (c.req.header('Upgrade')?.toLowerCase() !== 'websocket') {
8
+ return c.text('Expected WebSocket upgrade', 426);
9
+ }
10
+ if (!c.env.CHAT_SESSION) {
11
+ return c.text('CHAT_SESSION Durable Object binding is not configured', 503);
12
+ }
13
+
14
+ const id = c.env.CHAT_SESSION.idFromName('operator');
15
+ const stub = c.env.CHAT_SESSION.get(id);
16
+ return stub.fetch(c.req.raw);
17
+ });
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -15,6 +15,10 @@ const pages = new Hono<{ Bindings: Env }>();
15
15
  // ─── Landing ────────────────────────────────────────────────
16
16
 
17
17
  pages.get('/', (c) => {
18
+ return c.html(chatPage());
19
+ });
20
+
21
+ pages.get('/about', (c) => {
18
22
  return c.html(landingPage());
19
23
  });
20
24
 
@@ -44,7 +48,7 @@ pages.get('/manifest.json', (c) => {
44
48
  return c.json({
45
49
  name: 'AEGIS',
46
50
  short_name: 'AEGIS',
47
- start_url: '/chat',
51
+ start_url: '/',
48
52
  display: 'standalone',
49
53
  background_color: '#0a0a0f',
50
54
  theme_color: '#8b8bff',
File without changes
File without changes
package/src/types.ts CHANGED
@@ -4,6 +4,7 @@ export interface Env {
4
4
  DB: D1Database;
5
5
  AI: Ai;
6
6
  AEGIS_TOKEN: string;
7
+ CHAT_SESSION?: DurableObjectNamespace;
7
8
 
8
9
  // OAuth 2.1 (injected by OAuthProvider wrapper at runtime)
9
10
  OAUTH_PROVIDER: OAuthHelpers;
@@ -190,5 +191,10 @@ export interface MessageMetadata {
190
191
  confidence?: number;
191
192
  reclassified?: boolean;
192
193
  probeResult?: string;
194
+ grounded?: boolean;
195
+ sources?: string[];
196
+ unknowns?: string[];
197
+ searched?: string[];
198
+ unverifiedClaims?: string[];
193
199
  error?: boolean;
194
200
  }