neoagent 3.0.1-beta.13 → 3.0.1-beta.15
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/package.json +1 -1
- package/server/http/static.js +4 -2
- package/server/public/.last_build_id +1 -1
- package/server/public/flutter_bootstrap.js +1 -1
- package/server/public/main.dart.js +4 -4
- package/server/services/ai/loop/conversation_loop.js +9 -1
- package/server/services/ai/taskAnalysis.js +2 -0
- package/server/services/ai/tools.js +47 -1
- package/server/services/integrations/google/calendar.js +22 -14
- package/server/services/tasks/runtime.js +24 -5
package/package.json
CHANGED
package/server/http/static.js
CHANGED
|
@@ -78,8 +78,10 @@ function registerStaticRoutes(app) {
|
|
|
78
78
|
app.get(/^\/app(\/.*)?$/, serveFlutterApp);
|
|
79
79
|
|
|
80
80
|
// Landing page at /
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
if (fs.existsSync(path.join(LANDING_DIR, 'index.html'))) {
|
|
82
|
+
app.use(express.static(LANDING_DIR));
|
|
83
|
+
app.get('/', (req, res) => res.sendFile(path.join(LANDING_DIR, 'index.html')));
|
|
84
|
+
}
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
function serveFlutterApp(req, res) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8477300cc10bca3cb8a9dcb803dcb79d
|
|
@@ -37,6 +37,6 @@ _flutter.buildConfig = {"engineRevision":"77e2e94772b6eb43759e34ed1ad7da4674e19c
|
|
|
37
37
|
|
|
38
38
|
_flutter.loader.load({
|
|
39
39
|
serviceWorkerSettings: {
|
|
40
|
-
serviceWorkerVersion: "
|
|
40
|
+
serviceWorkerVersion: "462988837" /* Flutter's service worker is deprecated and will be removed in a future Flutter release. */
|
|
41
41
|
}
|
|
42
42
|
});
|
|
@@ -136637,7 +136637,7 @@ r===$&&A.b()
|
|
|
136637
136637
|
p.push(A.ji(q,A.jm(!1,new A.a_(B.v1,A.cV(new A.cA(B.jF,new A.a8s(r,q),q),q,q),q),!1,B.H,!0),q,q,0,0,0,q))}r=!1
|
|
136638
136638
|
if(!s.ay)if(!s.ch){r=s.e
|
|
136639
136639
|
r===$&&A.b()
|
|
136640
|
-
r=B.b.v("
|
|
136640
|
+
r=B.b.v("mql818rm-601e580").length!==0&&r.b}if(r){r=s.d
|
|
136641
136641
|
r===$&&A.b()
|
|
136642
136642
|
r=r.aC&&!r.au?84:0
|
|
136643
136643
|
s=s.e
|
|
@@ -142407,7 +142407,7 @@ $S:0}
|
|
|
142407
142407
|
A.a_G.prototype={}
|
|
142408
142408
|
A.Tp.prototype={
|
|
142409
142409
|
ng(a){var s=this
|
|
142410
|
-
if(B.b.v("
|
|
142410
|
+
if(B.b.v("mql818rm-601e580").length===0||s.a!=null)return
|
|
142411
142411
|
s.B2()
|
|
142412
142412
|
s.a=A.ow(B.Sk,new A.bec(s))},
|
|
142413
142413
|
B2(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f
|
|
@@ -142425,7 +142425,7 @@ if(!t.f.b(k)){s=1
|
|
|
142425
142425
|
break}i=J.a0(k,"buildId")
|
|
142426
142426
|
h=i==null?null:B.b.v(J.p(i))
|
|
142427
142427
|
j=h==null?"":h
|
|
142428
|
-
if(J.bf(j)===0||J.f(j,"
|
|
142428
|
+
if(J.bf(j)===0||J.f(j,"mql818rm-601e580")){s=1
|
|
142429
142429
|
break}n.b=!0
|
|
142430
142430
|
n.D()
|
|
142431
142431
|
p=2
|
|
@@ -142442,7 +142442,7 @@ case 2:return A.i(o.at(-1),r)}})
|
|
|
142442
142442
|
return A.k($async$B2,r)},
|
|
142443
142443
|
vK(){var s=0,r=A.l(t.H),q,p=2,o=[],n=this,m,l,k,j,i,h,g,f,e,d,c,b,a,a0,a1
|
|
142444
142444
|
var $async$vK=A.h(function(a2,a3){if(a2===1){o.push(a3)
|
|
142445
|
-
s=p}for(;;)switch(s){case 0:if(B.b.v("
|
|
142445
|
+
s=p}for(;;)switch(s){case 0:if(B.b.v("mql818rm-601e580").length===0||n.c){s=1
|
|
142446
142446
|
break}n.c=!0
|
|
142447
142447
|
n.D()
|
|
142448
142448
|
p=4
|
|
@@ -2121,6 +2121,12 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
2121
2121
|
lastContent = 'Widget snapshot updated.';
|
|
2122
2122
|
}
|
|
2123
2123
|
const messagingSent = runMeta?.messagingSent || false;
|
|
2124
|
+
const stagedProactiveReply = normalizeOutgoingMessage(
|
|
2125
|
+
runMeta?.stagedProactiveMessage?.content
|
|
2126
|
+
|| options?.deliveryState?.stagedProactiveMessage?.content
|
|
2127
|
+
|| '',
|
|
2128
|
+
options?.source || null,
|
|
2129
|
+
);
|
|
2124
2130
|
const lastToolWasMessaging = runMeta?.lastToolName === 'send_message' || runMeta?.lastToolName === 'make_call';
|
|
2125
2131
|
|
|
2126
2132
|
// Hermes _handle_max_iterations: if the run exhausted its step budget without a
|
|
@@ -2226,6 +2232,7 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
2226
2232
|
if (
|
|
2227
2233
|
!normalizeOutgoingMessage(lastContent, options?.source || null)
|
|
2228
2234
|
&& !messagingSent
|
|
2235
|
+
&& !stagedProactiveReply
|
|
2229
2236
|
&& runMeta?.widgetSnapshotSaved !== true
|
|
2230
2237
|
) {
|
|
2231
2238
|
const explicitNoResponse = (
|
|
@@ -2275,10 +2282,11 @@ async function runConversation(engine, userId, userMessage, options = {}, _model
|
|
|
2275
2282
|
const normalizedLastContent = normalizeOutgoingMessage(lastContent, options?.source || null);
|
|
2276
2283
|
let finalResponseText = messagingSent
|
|
2277
2284
|
? (sentMessageText || (normalizedLastContent ? lastContent.trim() : ''))
|
|
2278
|
-
: (normalizedLastContent ? lastContent.trim() : sentMessageText);
|
|
2285
|
+
: (normalizedLastContent ? lastContent.trim() : (stagedProactiveReply || sentMessageText));
|
|
2279
2286
|
const lastFinalDeliveryMessage = normalizeOutgoingMessage(
|
|
2280
2287
|
runMeta?.lastSentMessage
|
|
2281
2288
|
|| (Array.isArray(runMeta?.sentMessages) ? runMeta.sentMessages[runMeta.sentMessages.length - 1] : '')
|
|
2289
|
+
|| runMeta?.stagedProactiveMessage?.content
|
|
2282
2290
|
|| '',
|
|
2283
2291
|
options?.source || null
|
|
2284
2292
|
);
|
|
@@ -74,6 +74,8 @@ const VERIFIER_PROMPT_INSTRUCTIONS = [
|
|
|
74
74
|
'Verify whether the draft final reply is adequately supported by the gathered evidence.',
|
|
75
75
|
'If the evidence is insufficient, revise the reply so it states the uncertainty clearly instead of guessing.',
|
|
76
76
|
'Cross-check every concrete claim against tool status and output. Remove or rewrite claims that are contradicted by the evidence.',
|
|
77
|
+
'When the task or prompt names a specific entity to check (for example a city, account, person, date, source, or destination), preserve that target exactly. If the gathered evidence is for a different entity, treat the draft as needing revision or insufficient evidence.',
|
|
78
|
+
'When the task says to include something only if it is available or only if a condition is met, omit that section when the evidence does not show the condition was satisfied.',
|
|
77
79
|
'A non-zero execute_command exit code means partial or failed shell evidence. Do not treat later sections of a chained shell command as observed unless they were verified separately.',
|
|
78
80
|
'A successful send_message or make_call means outbound delivery succeeded in this run unless a later messaging tool failed.',
|
|
79
81
|
'Any claim that an outbound action already happened (sent/submitted/called/"already done") must be backed by a successful outbound tool execution in this run. If not backed, rewrite the reply to "not sent yet" and provide a draft or next concrete step.',
|
|
@@ -276,7 +276,33 @@ function getRunState(engine, runId) {
|
|
|
276
276
|
|
|
277
277
|
function hasAlreadySentProactiveMessage({ triggerSource, runState, deliveryState, allowMultipleProactiveMessages }) {
|
|
278
278
|
if (!isProactiveTrigger(triggerSource) || allowMultipleProactiveMessages) return false;
|
|
279
|
-
return Boolean(
|
|
279
|
+
return Boolean(
|
|
280
|
+
runState?.messagingSent
|
|
281
|
+
|| deliveryState?.messagingSent
|
|
282
|
+
|| runState?.proactiveMessageStaged
|
|
283
|
+
|| deliveryState?.proactiveMessageStaged
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function markProactiveMessageStaged({ runState, deliveryState, platform, to, content, purpose, mediaPath = null }) {
|
|
288
|
+
const staged = {
|
|
289
|
+
platform: String(platform || '').trim(),
|
|
290
|
+
to: String(to || '').trim(),
|
|
291
|
+
content: String(content || '').trim(),
|
|
292
|
+
purpose: String(purpose || '').trim().toLowerCase(),
|
|
293
|
+
mediaPath: mediaPath || null,
|
|
294
|
+
stagedAt: new Date().toISOString(),
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (runState) {
|
|
298
|
+
runState.proactiveMessageStaged = true;
|
|
299
|
+
runState.stagedProactiveMessage = staged;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (deliveryState) {
|
|
303
|
+
deliveryState.proactiveMessageStaged = true;
|
|
304
|
+
deliveryState.stagedProactiveMessage = staged;
|
|
305
|
+
}
|
|
280
306
|
}
|
|
281
307
|
|
|
282
308
|
function markProactiveMessageSent({ runState, deliveryState, content }) {
|
|
@@ -2485,6 +2511,26 @@ async function executeTool(toolName, args, context, engine) {
|
|
|
2485
2511
|
};
|
|
2486
2512
|
}
|
|
2487
2513
|
|
|
2514
|
+
if (isProactiveTrigger(triggerSource) && context.stageProactiveMessages === true && !suppressReply) {
|
|
2515
|
+
markProactiveMessageStaged({
|
|
2516
|
+
runState,
|
|
2517
|
+
deliveryState,
|
|
2518
|
+
platform: args.platform,
|
|
2519
|
+
to: args.to,
|
|
2520
|
+
content: normalizedMessage,
|
|
2521
|
+
purpose: args.purpose,
|
|
2522
|
+
mediaPath: args.media_path,
|
|
2523
|
+
});
|
|
2524
|
+
return {
|
|
2525
|
+
success: true,
|
|
2526
|
+
staged: true,
|
|
2527
|
+
purpose: normalizeSendMessagePurpose(args.purpose),
|
|
2528
|
+
platform: args.platform,
|
|
2529
|
+
to: args.to,
|
|
2530
|
+
content: normalizedMessage,
|
|
2531
|
+
};
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2488
2534
|
const sendResult = await manager.sendMessage(userId, args.platform, args.to, args.content, {
|
|
2489
2535
|
agentId,
|
|
2490
2536
|
mediaPath: args.media_path,
|
|
@@ -7,7 +7,7 @@ const calendarToolDefinitions = [
|
|
|
7
7
|
{
|
|
8
8
|
name: 'google_workspace_calendar_list_events',
|
|
9
9
|
access: 'read',
|
|
10
|
-
description: 'List or search Google Calendar events for the connected account. Results include all-day/multi-day events (birthdays, anniversaries, full-day markers) that overlap the window
|
|
10
|
+
description: 'List or search Google Calendar events for the connected account. Results include all-day/multi-day events (birthdays, anniversaries, full-day markers) that overlap the window. Each event has an allDay flag, timedCount/allDayCount summarize the mix, and timedEvents/nextTimedEvent expose the real timed appointments directly. For "what is scheduled / starting soon", decide from that single result instead of re-querying nearby windows to filter out all-day entries.',
|
|
11
11
|
parameters: {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
@@ -167,6 +167,25 @@ function summarizeEvent(event) {
|
|
|
167
167
|
};
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
function summarizeListedEvents(items) {
|
|
171
|
+
const events = Array.isArray(items) ? items.map(summarizeEvent) : [];
|
|
172
|
+
const timedEvents = events.filter((event) => !event.allDay);
|
|
173
|
+
const allDayEvents = events.filter((event) => event.allDay);
|
|
174
|
+
return {
|
|
175
|
+
count: events.length,
|
|
176
|
+
timedCount: timedEvents.length,
|
|
177
|
+
allDayCount: allDayEvents.length,
|
|
178
|
+
hasTimedEvents: timedEvents.length > 0,
|
|
179
|
+
hasOnlyAllDayEvents: timedEvents.length === 0 && allDayEvents.length > 0,
|
|
180
|
+
nextTimedEvent: timedEvents[0] || null,
|
|
181
|
+
timedEvents,
|
|
182
|
+
allDayEvents,
|
|
183
|
+
// Keep the legacy merged list for existing callers, but put timed items first so
|
|
184
|
+
// the most actionable event is visible without extra filtering.
|
|
185
|
+
events: [...timedEvents, ...allDayEvents],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
170
189
|
async function executeCalendarTool(toolName, args, auth) {
|
|
171
190
|
const calendar = google.calendar({ version: 'v3', auth });
|
|
172
191
|
const calendarId = String(args.calendar_id || 'primary').trim() || 'primary';
|
|
@@ -183,19 +202,7 @@ async function executeCalendarTool(toolName, args, auth) {
|
|
|
183
202
|
maxResults: Math.max(1, Math.min(Number(args.max_results) || 10, 50)),
|
|
184
203
|
});
|
|
185
204
|
const items = Array.isArray(response.data.items) ? response.data.items : [];
|
|
186
|
-
|
|
187
|
-
// Timed events first, then all-day markers, so the caller sees the entries it
|
|
188
|
-
// most likely cares about at the top. timedCount/allDayCount let a scheduled
|
|
189
|
-
// check conclude "nothing is actually scheduled" from one query instead of
|
|
190
|
-
// re-filtering the same flooded result set over and over.
|
|
191
|
-
const timed = events.filter((event) => !event.allDay);
|
|
192
|
-
const allDay = events.filter((event) => event.allDay);
|
|
193
|
-
return {
|
|
194
|
-
count: events.length,
|
|
195
|
-
timedCount: timed.length,
|
|
196
|
-
allDayCount: allDay.length,
|
|
197
|
-
events: [...timed, ...allDay],
|
|
198
|
-
};
|
|
205
|
+
return summarizeListedEvents(items);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
208
|
case 'google_workspace_calendar_create_event': {
|
|
@@ -300,4 +307,5 @@ module.exports = {
|
|
|
300
307
|
calendarToolDefinitions,
|
|
301
308
|
executeCalendarTool,
|
|
302
309
|
summarizeEvent,
|
|
310
|
+
summarizeListedEvents,
|
|
303
311
|
};
|
|
@@ -462,6 +462,8 @@ class TaskRuntime {
|
|
|
462
462
|
const deliveryState = {
|
|
463
463
|
messagingSent: false,
|
|
464
464
|
noResponse: false,
|
|
465
|
+
proactiveMessageStaged: false,
|
|
466
|
+
stagedProactiveMessage: null,
|
|
465
467
|
lastSentMessage: '',
|
|
466
468
|
sentMessages: [],
|
|
467
469
|
};
|
|
@@ -489,7 +491,7 @@ class TaskRuntime {
|
|
|
489
491
|
if (normalizedConfig.callTo) {
|
|
490
492
|
notifyHint = `\n\nThis task is configured to notify the user by phone. Use the make_call tool to call "${normalizedConfig.callTo}" with an appropriate greeting based on your findings. The configured greeting hint is: "${normalizedConfig.callGreeting || 'Hello, this is your task reminder.'}"`;
|
|
491
493
|
} else if (normalizedConfig.notifyPlatform && normalizedConfig.notifyTo) {
|
|
492
|
-
notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}" and set purpose="final_result" for a concrete useful outcome or purpose="blocker" for a real issue the user should know about. If nothing important or actionable changed, call send_message with purpose="no_response" and content="[NO RESPONSE]".${manualRun ? '' : ' For this automatic scheduled run, plain assistant text is internal only and is NOT delivered. You MUST end the run with exactly one explicit send_message decision (purpose="final_result", "blocker", or "no_response") — if you produce a real result, deliver it with send_message or it is lost.'}`;
|
|
494
|
+
notifyHint = `\n\nIf your task result is worth notifying the user about, send it proactively via send_message to platform="${normalizedConfig.notifyPlatform}" to="${normalizedConfig.notifyTo}" and set purpose="final_result" for a concrete useful outcome or purpose="blocker" for a real issue the user should know about. If nothing important or actionable changed, call send_message with purpose="no_response" and content="[NO RESPONSE]" exactly; never leave content blank for no_response. When a tool result already gives you summary fields or flags that answer the task, decide from that evidence instead of re-running nearby variants of the same lookup.${manualRun ? '' : ' For this automatic scheduled run, plain assistant text is internal only and is NOT delivered. You MUST end the run with exactly one explicit send_message decision (purpose="final_result", "blocker", or "no_response") — if you produce a real result, deliver it with send_message or it is lost.'}`;
|
|
493
495
|
}
|
|
494
496
|
|
|
495
497
|
const triggerPayloadText = executionMeta.triggerPayload
|
|
@@ -521,13 +523,14 @@ class TaskRuntime {
|
|
|
521
523
|
taskId,
|
|
522
524
|
deliveryState,
|
|
523
525
|
allowMultipleProactiveMessages: normalizedConfig.allowMultipleMessages === true || normalizedConfig.allow_multiple_messages === true,
|
|
526
|
+
stageProactiveMessages: true,
|
|
524
527
|
skipTaskAnalysis: true,
|
|
525
528
|
skipDeliverableWorkflow: true,
|
|
526
529
|
skipGlobalRecall: true,
|
|
527
530
|
skipConversationHistory: true,
|
|
528
531
|
skipConversationMaintenance: true,
|
|
529
532
|
skipRunContextPersistence: true,
|
|
530
|
-
skipVerifier:
|
|
533
|
+
skipVerifier: false,
|
|
531
534
|
stream: false,
|
|
532
535
|
context: executionMeta.triggerPayload || {},
|
|
533
536
|
};
|
|
@@ -841,13 +844,19 @@ class TaskRuntime {
|
|
|
841
844
|
if (!targets.length) return null;
|
|
842
845
|
const resultText = stringifyTaskResult(result).trim();
|
|
843
846
|
const resultLooksLikeError = Boolean(result?.error);
|
|
847
|
+
const stagedMessage = normalizeOutgoingMessageForPlatform(
|
|
848
|
+
deliveryState?.stagedProactiveMessage?.platform,
|
|
849
|
+
deliveryState?.stagedProactiveMessage?.content || '',
|
|
850
|
+
{ stripNoResponseMarker: false },
|
|
851
|
+
);
|
|
852
|
+
const explicitStagedDelivery = deliveryState?.proactiveMessageStaged === true && Boolean(stagedMessage);
|
|
844
853
|
// A forced terminal wrap-up (read-only/blocked hard-stop) is the model's final
|
|
845
854
|
// answer produced WITHOUT the ability to call send_message itself. Gating it
|
|
846
855
|
// would silently drop a stuck scheduled task's result, so deliver it even on an
|
|
847
856
|
// automatic run. Ordinary mid-run plain text (model had send_message available
|
|
848
857
|
// and chose not to use it) is still gated below.
|
|
849
858
|
const forcedTerminal = deliveryState?.terminalWrapup === true && Boolean(resultText);
|
|
850
|
-
if (!allowPlainResultFallback && !resultLooksLikeError && !forcedTerminal) {
|
|
859
|
+
if (!allowPlainResultFallback && !resultLooksLikeError && !forcedTerminal && !explicitStagedDelivery) {
|
|
851
860
|
// Automatic run produced substantive text but never made an explicit
|
|
852
861
|
// send_message decision (a deliberate no_response would have short-circuited
|
|
853
862
|
// above). We suppress to avoid recurring-check spam, but surface it so a
|
|
@@ -874,10 +883,17 @@ class TaskRuntime {
|
|
|
874
883
|
}
|
|
875
884
|
|
|
876
885
|
let lastError = null;
|
|
877
|
-
|
|
886
|
+
const resolvedTargets = explicitStagedDelivery
|
|
887
|
+
? [{
|
|
888
|
+
platform: deliveryState.stagedProactiveMessage.platform,
|
|
889
|
+
to: deliveryState.stagedProactiveMessage.to,
|
|
890
|
+
mediaPath: deliveryState.stagedProactiveMessage.mediaPath || null,
|
|
891
|
+
}]
|
|
892
|
+
: targets;
|
|
893
|
+
for (const target of resolvedTargets) {
|
|
878
894
|
const message = normalizeOutgoingMessageForPlatform(
|
|
879
895
|
target.platform,
|
|
880
|
-
resultText,
|
|
896
|
+
resultText || stagedMessage,
|
|
881
897
|
{ stripNoResponseMarker: false },
|
|
882
898
|
);
|
|
883
899
|
if (!message || message.toUpperCase() === '[NO RESPONSE]') return null;
|
|
@@ -893,10 +909,13 @@ class TaskRuntime {
|
|
|
893
909
|
try {
|
|
894
910
|
const sendResult = await manager.sendMessage(userId, target.platform, target.to, message, {
|
|
895
911
|
agentId,
|
|
912
|
+
mediaPath: target.mediaPath || null,
|
|
896
913
|
runId: result?.runId || null,
|
|
897
914
|
persistConversation: true,
|
|
898
915
|
});
|
|
899
916
|
deliveryState.messagingSent = true;
|
|
917
|
+
deliveryState.proactiveMessageStaged = false;
|
|
918
|
+
deliveryState.stagedProactiveMessage = null;
|
|
900
919
|
deliveryState.lastSentMessage = message;
|
|
901
920
|
if (!Array.isArray(deliveryState.sentMessages)) {
|
|
902
921
|
deliveryState.sentMessages = [];
|