neoagent 3.2.1-beta.1 → 3.2.1-beta.10
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/extensions/chrome-browser/background.mjs +318 -88
- package/extensions/chrome-browser/http.mjs +136 -0
- package/extensions/chrome-browser/protocol.mjs +654 -90
- package/flutter_app/lib/main_chat.dart +118 -739
- package/flutter_app/lib/main_controller.dart +111 -20
- package/flutter_app/lib/main_integrations.dart +607 -8
- package/flutter_app/lib/main_models.dart +3 -0
- package/flutter_app/lib/main_operations.dart +334 -321
- package/flutter_app/lib/main_security.dart +266 -112
- package/flutter_app/lib/main_settings.dart +4 -3
- package/flutter_app/lib/main_shared.dart +14 -11
- package/flutter_app/lib/src/backend_client.dart +78 -0
- package/flutter_app/lib/src/desktop_companion_actions.dart +185 -31
- package/flutter_app/lib/src/desktop_companion_io.dart +319 -86
- package/flutter_app/windows/runner/flutter_window.cpp +143 -32
- package/landing/index.html +3 -1
- package/lib/manager.js +106 -89
- package/lib/schema_migrations.js +115 -13
- package/package.json +30 -15
- package/runtime/paths.js +49 -5
- package/server/db/database.js +2 -2
- package/server/guest-agent.cli.package.json +13 -0
- package/server/guest_agent.js +85 -40
- package/server/http/middleware.js +24 -0
- package/server/http/routes.js +11 -6
- package/server/public/.last_build_id +1 -1
- package/server/public/assets/fonts/MaterialIcons-Regular.otf +0 -0
- package/server/public/flutter_bootstrap.js +2 -2
- package/server/public/main.dart.js +73083 -72209
- package/server/routes/admin.js +1 -1
- package/server/routes/android.js +30 -34
- package/server/routes/browser.js +23 -15
- package/server/routes/desktop.js +18 -1
- package/server/routes/integrations.js +107 -1
- package/server/routes/memory.js +1 -0
- package/server/routes/settings.js +16 -5
- package/server/routes/social_reach.js +12 -3
- package/server/routes/social_video.js +4 -0
- package/server/services/agents/manager.js +1 -1
- package/server/services/ai/capabilityHealth.js +62 -96
- package/server/services/ai/compaction.js +7 -2
- package/server/services/ai/history.js +45 -6
- package/server/services/ai/integrated_tools/http_request.js +8 -0
- package/server/services/ai/loop/agent_engine_core.js +390 -162
- package/server/services/ai/loop/blank_recovery.js +5 -4
- package/server/services/ai/loop/callbacks.js +1 -0
- package/server/services/ai/loop/completion_judge.js +121 -5
- package/server/services/ai/loop/conversation_loop.js +510 -332
- package/server/services/ai/loop/messaging_delivery.js +129 -57
- package/server/services/ai/loop/model_call_guard.js +91 -0
- package/server/services/ai/loop/model_io.js +20 -45
- package/server/services/ai/loop/progress_classification.js +2 -0
- package/server/services/ai/loop/tool_dispatch.js +19 -8
- package/server/services/ai/loopPolicy.js +48 -21
- package/server/services/ai/messagingFallback.js +17 -17
- package/server/services/ai/model_discovery.js +227 -0
- package/server/services/ai/model_failure_cache.js +108 -0
- package/server/services/ai/model_identity.js +71 -0
- package/server/services/ai/models.js +68 -163
- package/server/services/ai/providerRetry.js +17 -59
- package/server/services/ai/provider_selector.js +166 -0
- package/server/services/ai/providers/anthropic.js +2 -2
- package/server/services/ai/providers/claudeCode.js +21 -33
- package/server/services/ai/providers/githubCopilot.js +41 -20
- package/server/services/ai/providers/google.js +135 -97
- package/server/services/ai/providers/grok.js +4 -3
- package/server/services/ai/providers/grokOauth.js +19 -27
- package/server/services/ai/providers/nvidia.js +10 -5
- package/server/services/ai/providers/ollama.js +111 -84
- package/server/services/ai/providers/ollama_stream.js +142 -0
- package/server/services/ai/providers/openai.js +39 -5
- package/server/services/ai/providers/openaiCodex.js +11 -4
- package/server/services/ai/providers/openrouter.js +29 -7
- package/server/services/ai/providers/provider_error.js +36 -0
- package/server/services/ai/settings.js +26 -2
- package/server/services/ai/systemPrompt.js +19 -12
- package/server/services/ai/taskAnalysis.js +58 -10
- package/server/services/ai/terminal_reply.js +18 -0
- package/server/services/ai/toolEvidence.js +350 -29
- package/server/services/ai/tools.js +190 -111
- package/server/services/android/controller.js +770 -237
- package/server/services/android/process.js +140 -0
- package/server/services/android/sdk_download.js +143 -0
- package/server/services/android/uia.js +6 -5
- package/server/services/artifacts/store.js +24 -0
- package/server/services/browser/controller.js +843 -385
- package/server/services/browser/extension/gateway.js +40 -16
- package/server/services/browser/extension/protocol.js +15 -1
- package/server/services/browser/extension/provider.js +71 -47
- package/server/services/browser/extension/registry.js +155 -34
- package/server/services/cli/executor.js +62 -9
- package/server/services/credentials/bitwarden_cli.js +322 -0
- package/server/services/credentials/broker.js +594 -0
- package/server/services/desktop/gateway.js +41 -4
- package/server/services/desktop/protocol.js +3 -0
- package/server/services/desktop/provider.js +39 -42
- package/server/services/desktop/registry.js +137 -52
- package/server/services/integrations/bitwarden/constants.js +14 -0
- package/server/services/integrations/bitwarden/provider.js +197 -0
- package/server/services/integrations/bitwarden/snapshot.js +65 -0
- package/server/services/integrations/figma/provider.js +78 -12
- package/server/services/integrations/github/common.js +11 -6
- package/server/services/integrations/github/provider.js +52 -53
- package/server/services/integrations/google/provider.js +55 -19
- package/server/services/integrations/home_assistant/network.js +17 -20
- package/server/services/integrations/home_assistant/provider.js +7 -5
- package/server/services/integrations/home_assistant/tools.js +17 -5
- package/server/services/integrations/http.js +51 -0
- package/server/services/integrations/manager.js +159 -53
- package/server/services/integrations/microsoft/provider.js +80 -13
- package/server/services/integrations/neoarchive/provider.js +55 -29
- package/server/services/integrations/neorecall/client.js +17 -10
- package/server/services/integrations/neorecall/provider.js +20 -11
- package/server/services/integrations/notion/provider.js +16 -13
- package/server/services/integrations/oauth_provider.js +115 -51
- package/server/services/integrations/registry.js +2 -0
- package/server/services/integrations/slack/provider.js +98 -9
- package/server/services/integrations/spotify/provider.js +67 -71
- package/server/services/integrations/trello/provider.js +21 -7
- package/server/services/integrations/weather/provider.js +18 -12
- package/server/services/integrations/whatsapp/provider.js +76 -16
- package/server/services/manager.js +110 -1
- package/server/services/memory/embedding_index.js +20 -8
- package/server/services/memory/embeddings.js +151 -90
- package/server/services/memory/ingestion.js +50 -9
- package/server/services/memory/ingestion_documents.js +13 -3
- package/server/services/memory/manager.js +52 -19
- package/server/services/messaging/automation.js +85 -10
- package/server/services/messaging/formatting_guides.js +7 -4
- package/server/services/messaging/http_platforms.js +33 -13
- package/server/services/messaging/inbound_queue.js +78 -24
- package/server/services/messaging/inbound_store.js +224 -0
- package/server/services/messaging/manager.js +326 -51
- package/server/services/messaging/typing_keepalive.js +5 -2
- package/server/services/messaging/whatsapp.js +22 -14
- package/server/services/network/http.js +210 -0
- package/server/services/network/safe_request.js +307 -0
- package/server/services/runtime/backends/local-vm.js +227 -67
- package/server/services/runtime/docker-vm-manager.js +9 -0
- package/server/services/runtime/guest_bootstrap.js +30 -4
- package/server/services/runtime/guest_image.js +43 -12
- package/server/services/runtime/manager.js +77 -23
- package/server/services/runtime/validation.js +7 -6
- package/server/services/security/tool_categories.js +6 -0
- package/server/services/social_reach/channels/github.js +10 -4
- package/server/services/social_reach/channels/reddit.js +4 -4
- package/server/services/social_reach/channels/rss.js +2 -2
- package/server/services/social_reach/channels/social_video.js +12 -7
- package/server/services/social_reach/channels/v2ex.js +21 -8
- package/server/services/social_reach/channels/x.js +2 -2
- package/server/services/social_reach/channels/xueqiu.js +5 -5
- package/server/services/social_reach/service.js +9 -6
- package/server/services/social_reach/utils.js +65 -14
- package/server/services/social_video/service.js +160 -50
- package/server/services/tasks/integration_runtime.js +18 -8
- package/server/services/tasks/runtime.js +39 -4
- package/server/services/voice/agentBridge.js +17 -4
- package/server/services/voice/bufferedLiveRelayAdapter.js +5 -0
- package/server/services/voice/liveSession.js +31 -0
- package/server/services/voice/message.js +1 -1
- package/server/services/voice/openaiSpeech.js +33 -8
- package/server/services/voice/providers.js +233 -151
- package/server/services/voice/runtime.js +2 -2
- package/server/services/voice/runtimeManager.js +118 -20
- package/server/services/voice/turnRunner.js +6 -0
- package/server/services/wearable/firmware_manifest.js +51 -13
- package/server/services/wearable/service.js +1 -0
- package/server/utils/abort.js +96 -0
- package/server/utils/cloud-security.js +110 -3
- package/server/utils/files.js +31 -0
- package/server/utils/image_payload.js +95 -0
- package/server/utils/retry.js +107 -0
|
@@ -33,9 +33,9 @@ function shouldContinueAfterRecoverableToolFailure({
|
|
|
33
33
|
if (Number(remainingIterations || 0) <= 0) return false;
|
|
34
34
|
const failedExecution = latestFailedToolExecution(toolExecutions);
|
|
35
35
|
if (!isRecoverableInternalToolFailure(failedExecution)) return false;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
return
|
|
36
|
+
// Structural only: recoverable internal tool failure + remaining budget.
|
|
37
|
+
// Do not phrase-match the draft reply.
|
|
38
|
+
return Boolean(normalizeOutgoingMessage(lastContent || '') || failedExecution);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
|
|
@@ -46,7 +46,8 @@ function buildBlankAfterToolFailureGuidance(toolExecutions = []) {
|
|
|
46
46
|
return [
|
|
47
47
|
`The previous tool "${toolName}" failed with: ${summarizeForLog(failure, 240)}.`,
|
|
48
48
|
'The latest assistant turn returned no user-facing answer and no tool call, so the task is not terminal.',
|
|
49
|
-
'Continue with the next safe recovery action: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
|
|
49
|
+
'Continue with the next safe recovery action now in this same turn: retry with corrected arguments, use another available tool, verify from existing evidence, or report a real blocker only if no autonomous path remains.',
|
|
50
|
+
'Do not invent a finished result. Prefer a concrete recovery step or a truthful partial answer over silence.',
|
|
50
51
|
].join(' ');
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -65,6 +65,7 @@ async function publishInterimUpdate(engine, {
|
|
|
65
65
|
persistConversation: true,
|
|
66
66
|
metadata,
|
|
67
67
|
deliveryKind: 'interim',
|
|
68
|
+
signal: runMeta.abortController?.signal || null,
|
|
68
69
|
});
|
|
69
70
|
requireSuccessfulMessagingDelivery(deliveryResult, 'Interim messaging delivery');
|
|
70
71
|
} else if (triggerSource === 'voice_live') {
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
const { normalizeCompletionConfidence } = require('../completion');
|
|
4
4
|
const { normalizeOutgoingMessage } = require('../messagingFallback');
|
|
5
5
|
const {
|
|
6
|
+
assessResearchAdequacy,
|
|
7
|
+
formatResearchAdequacyGuidance,
|
|
6
8
|
summarizeAvailableTools,
|
|
7
9
|
summarizeToolExecutions,
|
|
8
10
|
} = require('../toolEvidence');
|
|
@@ -194,8 +196,15 @@ function buildCompletionDecisionPrompt({
|
|
|
194
196
|
lastReply,
|
|
195
197
|
iteration,
|
|
196
198
|
maxIterations,
|
|
199
|
+
analysis = null,
|
|
200
|
+
researchAdequacy = null,
|
|
197
201
|
}) {
|
|
198
202
|
const draftReply = normalizeOutgoingMessage(lastReply) || '';
|
|
203
|
+
const adequacy = researchAdequacy || assessResearchAdequacy({
|
|
204
|
+
analysis,
|
|
205
|
+
goalContext,
|
|
206
|
+
toolExecutions,
|
|
207
|
+
});
|
|
199
208
|
const lines = [
|
|
200
209
|
'Return JSON only.',
|
|
201
210
|
'Decide whether this run should continue autonomously or stop now.',
|
|
@@ -210,13 +219,33 @@ function buildCompletionDecisionPrompt({
|
|
|
210
219
|
'- A tool-specific API error, timeout, rate limit, or missing result inside this run is usually "continue", not "blocked", if any other available tool could still make progress.',
|
|
211
220
|
'- Repeated read-only inspection that has already established the relevant object is absent or unchanged is not progress. Accept a concise complete/blocker reply instead of requiring more searching.',
|
|
212
221
|
`- If completion_confidence_required is ${goalContext.effectiveCompletionConfidence} and the latest draft depends on unverified assumptions, use "continue" so the run can gather evidence, inspect state, or narrow the reply.`,
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
'- When research intensity is light or deep, use "continue" until the required primary/source coverage and target coverage are met, or the draft is an explicit blocker naming exactly what could not be verified.',
|
|
223
|
+
'- Search snippets, memory, and model priors are leads, not completion evidence. Prefer opened/fetched primary sources before "complete".',
|
|
224
|
+
'- If the latest draft invents entities, products, people, files, results, or actions that are not supported by tool evidence, use "continue" so the run can gather evidence or rewrite into a truthful partial/blocker answer.',
|
|
225
|
+
'- A polished-sounding answer is not complete if key requested targets still lack direct evidence.',
|
|
226
|
+
'- If the latest draft only announces unfinished work, promises a future update, or asks the user to wait without a concrete result or blocker, use "continue" so the run keeps acting.',
|
|
227
|
+
'- If the latest draft asks for missing required user input, confirmation, or a choice needed to proceed, use "blocked" so the run waits instead of repeating the same ask.',
|
|
218
228
|
];
|
|
219
229
|
|
|
230
|
+
if (adequacy.intensity !== 'none') {
|
|
231
|
+
lines.push(
|
|
232
|
+
`- Research intensity for this run is ${adequacy.intensity}. Current coverage: primary=${adequacy.primarySourceCount}/${adequacy.requiredPrimarySources}, secondary=${adequacy.secondarySourceCount}, targets_covered=${adequacy.coveredTargets.length}/${Math.max(adequacy.requiredTargetCoverage, adequacy.targets.length)}.`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (adequacy.adequate === false) {
|
|
236
|
+
lines.push(
|
|
237
|
+
`- Research is still incomplete (${adequacy.reason}). Use "continue" unless the latest draft is an explicit blocker naming the exact missing evidence.`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (triggerSource === 'messaging' && messagingSent) {
|
|
242
|
+
lines.push('- A final reply was already delivered via send_message. Use "complete" unless concrete task work remains.');
|
|
243
|
+
} else if (triggerSource === 'messaging') {
|
|
244
|
+
lines.push('- For messaging, do not stop on a partial status message. Continue unless the task is actually complete or externally blocked.');
|
|
245
|
+
} else {
|
|
246
|
+
lines.push('- Do not stop just because you wrote a status update. Continue unless the task is actually complete or externally blocked.');
|
|
247
|
+
}
|
|
248
|
+
|
|
220
249
|
lines.push(
|
|
221
250
|
goalContext.effectiveGoal ? `Goal: ${goalContext.effectiveGoal}` : '',
|
|
222
251
|
goalContext.persistedGoalPrompt,
|
|
@@ -227,6 +256,16 @@ function buildCompletionDecisionPrompt({
|
|
|
227
256
|
`Current iteration: ${iteration} of ${maxIterations}.`,
|
|
228
257
|
`Available tools in this run: ${summarizeAvailableTools(tools) || 'none'}`,
|
|
229
258
|
`Recent tool evidence:\n${summarizeToolExecutions(toolExecutions, 8) || 'none'}`,
|
|
259
|
+
adequacy.intensity !== 'none'
|
|
260
|
+
? `Research adequacy: intensity=${adequacy.intensity}; adequate=${adequacy.adequate}; reason=${adequacy.reason}`
|
|
261
|
+
: '',
|
|
262
|
+
adequacy.targets.length
|
|
263
|
+
? `Research targets: ${adequacy.targets.join('; ')}`
|
|
264
|
+
: '',
|
|
265
|
+
adequacy.uncoveredTargets.length
|
|
266
|
+
? `Uncovered research targets: ${adequacy.uncoveredTargets.join('; ')}`
|
|
267
|
+
: '',
|
|
268
|
+
formatResearchAdequacyGuidance(adequacy),
|
|
230
269
|
`Latest draft reply:\n${draftReply || '(empty)'}`,
|
|
231
270
|
);
|
|
232
271
|
return lines.filter(Boolean).join('\n');
|
|
@@ -241,6 +280,39 @@ function normalizeCompletionDecision(raw, fallbackStatus = 'continue') {
|
|
|
241
280
|
};
|
|
242
281
|
}
|
|
243
282
|
|
|
283
|
+
function enforceTerminalReplyDecision(decision, lastReply, options = {}) {
|
|
284
|
+
// Natural-language terminality is judged by the model completion decision.
|
|
285
|
+
// Runtime only enforces structural evidence contracts (research adequacy).
|
|
286
|
+
const researchAdequacy = options.researchAdequacy
|
|
287
|
+
|| assessResearchAdequacy({
|
|
288
|
+
analysis: options.analysis || null,
|
|
289
|
+
goalContext: options.goalContext || null,
|
|
290
|
+
toolExecutions: options.toolExecutions || [],
|
|
291
|
+
});
|
|
292
|
+
if (
|
|
293
|
+
researchAdequacy
|
|
294
|
+
&& researchAdequacy.adequate === false
|
|
295
|
+
&& (decision?.status === 'complete' || decision?.status === 'blocked')
|
|
296
|
+
) {
|
|
297
|
+
// Allow true blocked outcomes only when the model already chose blocked AND
|
|
298
|
+
// research cannot progress further (no remaining targets/primary gap that
|
|
299
|
+
// tools could still cover). Otherwise force continue for evidence gathering.
|
|
300
|
+
if (
|
|
301
|
+
decision?.status === 'blocked'
|
|
302
|
+
&& researchAdequacy.uncoveredTargets.length === 0
|
|
303
|
+
&& researchAdequacy.primarySourceCount >= researchAdequacy.requiredPrimarySources
|
|
304
|
+
) {
|
|
305
|
+
return decision;
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
status: 'continue',
|
|
309
|
+
reason: researchAdequacy.reason
|
|
310
|
+
|| 'Research evidence is still incomplete for the requested targets; continue gathering sources before finishing.',
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return decision;
|
|
314
|
+
}
|
|
315
|
+
|
|
244
316
|
// Intentionally lightweight (200-token cap, self-contained) so the model can
|
|
245
317
|
// answer cold without re-reading full conversation history.
|
|
246
318
|
function buildChurnAssessmentPrompt({
|
|
@@ -249,7 +321,14 @@ function buildChurnAssessmentPrompt({
|
|
|
249
321
|
goalContext,
|
|
250
322
|
toolExecutions,
|
|
251
323
|
iteration,
|
|
324
|
+
analysis = null,
|
|
325
|
+
researchAdequacy = null,
|
|
252
326
|
}) {
|
|
327
|
+
const adequacy = researchAdequacy || assessResearchAdequacy({
|
|
328
|
+
analysis,
|
|
329
|
+
goalContext,
|
|
330
|
+
toolExecutions,
|
|
331
|
+
});
|
|
253
332
|
const lines = [
|
|
254
333
|
'Return JSON only.',
|
|
255
334
|
'Self-assess your current loop state — are you making genuine progress or spinning?',
|
|
@@ -263,15 +342,50 @@ function buildChurnAssessmentPrompt({
|
|
|
263
342
|
: '',
|
|
264
343
|
`Iteration: ${iteration}`,
|
|
265
344
|
`Recent tool evidence:\n${summarizeToolExecutions(toolExecutions, 6) || 'none'}`,
|
|
345
|
+
adequacy.intensity !== 'none'
|
|
346
|
+
? `Research adequacy: intensity=${adequacy.intensity}; adequate=${adequacy.adequate}; covered=${adequacy.coveredTargets.length}/${Math.max(adequacy.requiredTargetCoverage, adequacy.targets.length)}; primary=${adequacy.primarySourceCount}/${adequacy.requiredPrimarySources}.`
|
|
347
|
+
: '',
|
|
348
|
+
adequacy.uncoveredTargets.length
|
|
349
|
+
? `Still uncovered research targets: ${adequacy.uncoveredTargets.join('; ')}.`
|
|
350
|
+
: '',
|
|
266
351
|
'',
|
|
267
352
|
'Assessment rules:',
|
|
268
353
|
'- "progressing": You are systematically gathering necessary context and the next concrete action is already determined — you know exactly what to do next.',
|
|
269
354
|
'- "churn": You are re-reading/re-searching information already in context, or exploring without a clear next concrete step. Accept the nudge and act.',
|
|
270
355
|
'- "blocked": No concrete action is available in this run. You have all the evidence needed to deliver a truthful final answer or a specific external blocker.',
|
|
356
|
+
'- For multi-target research, keep "progressing" while uncovered targets remain and a fresh primary source can still be opened. Do not mark "blocked" just because you have partial notes.',
|
|
357
|
+
'- Re-querying the same snippet source for an already covered target is "churn". Opening a different primary source for an uncovered target is "progressing".',
|
|
358
|
+
adequacy.adequate === false
|
|
359
|
+
? '- Research adequacy is currently incomplete. Prefer "progressing" if a new primary source for an uncovered target is still available; use "churn" only for repeated identical reads; do not use "blocked" unless tools cannot reach remaining targets.'
|
|
360
|
+
: '',
|
|
271
361
|
];
|
|
272
362
|
return lines.filter(Boolean).join('\n');
|
|
273
363
|
}
|
|
274
364
|
|
|
365
|
+
function enforceChurnAssessment(assessment, options = {}) {
|
|
366
|
+
const normalized = normalizeChurnAssessment(assessment);
|
|
367
|
+
const researchAdequacy = options.researchAdequacy
|
|
368
|
+
|| assessResearchAdequacy({
|
|
369
|
+
analysis: options.analysis || null,
|
|
370
|
+
goalContext: options.goalContext || null,
|
|
371
|
+
toolExecutions: options.toolExecutions || [],
|
|
372
|
+
});
|
|
373
|
+
if (
|
|
374
|
+
researchAdequacy
|
|
375
|
+
&& researchAdequacy.adequate === false
|
|
376
|
+
&& researchAdequacy.intensity !== 'none'
|
|
377
|
+
&& normalized.assessment === 'blocked'
|
|
378
|
+
&& (researchAdequacy.uncoveredTargets?.length > 0 || researchAdequacy.primarySourceCount < researchAdequacy.requiredPrimarySources)
|
|
379
|
+
) {
|
|
380
|
+
return {
|
|
381
|
+
assessment: 'progressing',
|
|
382
|
+
reason: researchAdequacy.reason
|
|
383
|
+
|| 'Research targets remain uncovered; keep gathering primary sources instead of force-finishing.',
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
return normalized;
|
|
387
|
+
}
|
|
388
|
+
|
|
275
389
|
function normalizeChurnAssessment(raw) {
|
|
276
390
|
const allowed = new Set(['progressing', 'churn', 'blocked']);
|
|
277
391
|
const assessment = String(raw?.assessment || '').trim().toLowerCase();
|
|
@@ -285,9 +399,11 @@ module.exports = {
|
|
|
285
399
|
buildChurnAssessmentPrompt,
|
|
286
400
|
buildCompletionDecisionPrompt,
|
|
287
401
|
buildGoalContractPrompt,
|
|
402
|
+
enforceTerminalReplyDecision,
|
|
288
403
|
goalContractFromAnalysis,
|
|
289
404
|
goalContractFromPlan,
|
|
290
405
|
mergeGoalContracts,
|
|
406
|
+
enforceChurnAssessment,
|
|
291
407
|
normalizeChurnAssessment,
|
|
292
408
|
normalizeCompletionDecision,
|
|
293
409
|
normalizeGoalContract,
|