@ouro.bot/cli 0.1.0-alpha.506 → 0.1.0-alpha.508
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/changelog.json +15 -0
- package/dist/heart/session-events.js +19 -0
- package/dist/repertoire/tools-trip.js +66 -0
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.508",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New `trip_remove_leg` tool. The trip ledger had `trip_upsert`, `trip_attach_evidence`, and `trip_update_leg`, but no first-class way to drop a leg — the agent had to re-emit the entire trip record minus that leg (fragile, easy to lose evidence). Real workflow that needed it: the user cancelled a hotel booking; #620's e2e test exercised the add path but there was no way to model the cancel.",
|
|
8
|
+
"`trip_remove_leg(tripId, legId, updatedAt, reason?)` finds the leg, drops it from `legs[]`, bumps the trip's `updatedAt`, and emits `trips.leg_removed` (info) carrying tripId/legId/kind/reason. Rejects when the leg id is unknown (so accidental no-ops are visible) and when the trip is missing (returns the same `trip not found` shape as the other tools). Tool registry up to 75 (now 8 trip tools — snapshot updated, H10 contract list includes the new name).",
|
|
9
|
+
"5 tests cover happy-path removal with leg-count assertion via `trip_get`, unknown-leg rejection, missing-trip propagation, the three required-field validation paths, plus the existing stranger-ctx trust block now extended to include `trip_remove_leg`."
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"version": "0.1.0-alpha.507",
|
|
14
|
+
"changes": [
|
|
15
|
+
"Drop the second of two consecutive assistant messages with byte-identical content in `repairSessionMessages`. The existing back-to-back-assistant repair concatenated content with `\\n\\n`, which produced visible duplicate text on the surface when the second message was a retry/double-persist artifact (a common shape when a turn is interrupted mid-save and re-emitted).",
|
|
16
|
+
"Behavior change is narrow: trim-equality only, both sides must have non-empty content, and the second assistant must not have its own tool_calls. Different content still concatenates (legitimate continuation). Empty-content assistants still concatenate (the existing test case for null+undefined is preserved). Emits `mind.session_duplicate_assistant_dropped` (info) with the count so #622 (nerves-review) can show how often it fires in real traffic. 2 new tests cover the dedup path and the no-false-positive case (different content concatenates as before)."
|
|
17
|
+
]
|
|
18
|
+
},
|
|
4
19
|
{
|
|
5
20
|
"version": "0.1.0-alpha.506",
|
|
6
21
|
"changes": [
|
|
@@ -355,18 +355,37 @@ function repairSessionMessages(messages) {
|
|
|
355
355
|
if (violations.length === 0)
|
|
356
356
|
return normalized.map(toProviderMessage);
|
|
357
357
|
const result = [];
|
|
358
|
+
let duplicateAssistantsDropped = 0;
|
|
358
359
|
for (const msg of normalized) {
|
|
359
360
|
if (msg.role === "assistant" && result.length > 0) {
|
|
360
361
|
const prev = result[result.length - 1];
|
|
361
362
|
if (prev.role === "assistant" && prev.toolCalls.length === 0) {
|
|
362
363
|
const prevContent = contentText(prev.content);
|
|
363
364
|
const curContent = contentText(msg.content);
|
|
365
|
+
// Drop the second of two consecutive assistants when the content is
|
|
366
|
+
// byte-identical (after trim) — that's a retry/double-persist artifact,
|
|
367
|
+
// not legitimate continuation. Concatenating them produced visible
|
|
368
|
+
// duplicate text in surfaces. Empty strings still concatenate (could
|
|
369
|
+
// be "" + real content).
|
|
370
|
+
if (prevContent.trim().length > 0 && prevContent.trim() === curContent.trim() && msg.toolCalls.length === 0) {
|
|
371
|
+
duplicateAssistantsDropped += 1;
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
364
374
|
prev.content = `${prevContent}\n\n${curContent}`;
|
|
365
375
|
continue;
|
|
366
376
|
}
|
|
367
377
|
}
|
|
368
378
|
result.push(msg);
|
|
369
379
|
}
|
|
380
|
+
if (duplicateAssistantsDropped > 0) {
|
|
381
|
+
(0, runtime_1.emitNervesEvent)({
|
|
382
|
+
level: "info",
|
|
383
|
+
event: "mind.session_duplicate_assistant_dropped",
|
|
384
|
+
component: "mind",
|
|
385
|
+
message: "dropped consecutive assistant messages with identical content (retry/double-persist artifact)",
|
|
386
|
+
meta: { count: duplicateAssistantsDropped },
|
|
387
|
+
});
|
|
388
|
+
}
|
|
370
389
|
(0, runtime_1.emitNervesEvent)({
|
|
371
390
|
level: "info",
|
|
372
391
|
event: "mind.session_invariant_repair",
|
|
@@ -324,6 +324,72 @@ exports.tripToolDefinitions = [
|
|
|
324
324
|
},
|
|
325
325
|
summaryKeys: ["tripId", "legId"],
|
|
326
326
|
},
|
|
327
|
+
{
|
|
328
|
+
tool: {
|
|
329
|
+
type: "function",
|
|
330
|
+
function: {
|
|
331
|
+
name: "trip_remove_leg",
|
|
332
|
+
description: "Remove a leg from a trip. Use when a leg was added by mistake or the booking was cancelled. Updates the trip's updatedAt. Rejects when the leg id is unknown so accidental no-op removals are visible.",
|
|
333
|
+
parameters: {
|
|
334
|
+
type: "object",
|
|
335
|
+
properties: {
|
|
336
|
+
tripId: { type: "string", description: "Canonical trip id." },
|
|
337
|
+
legId: { type: "string", description: "Leg id within the trip to drop." },
|
|
338
|
+
updatedAt: { type: "string", description: "ISO timestamp for the trip's updatedAt." },
|
|
339
|
+
reason: { type: "string", description: "Why the leg is being removed. Logged in nerves for audit." },
|
|
340
|
+
},
|
|
341
|
+
required: ["tripId", "legId", "updatedAt"],
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
handler: async (args, ctx) => {
|
|
346
|
+
if (!trustAllowsTripAccess(ctx))
|
|
347
|
+
return "trip ledger is private; this tool is only available in trusted contexts.";
|
|
348
|
+
const tripId = args.tripId;
|
|
349
|
+
const legId = args.legId;
|
|
350
|
+
const updatedAt = args.updatedAt;
|
|
351
|
+
if (typeof tripId !== "string" || tripId.length === 0)
|
|
352
|
+
return "tripId is required.";
|
|
353
|
+
if (typeof legId !== "string" || legId.length === 0)
|
|
354
|
+
return "legId is required.";
|
|
355
|
+
if (typeof updatedAt !== "string" || updatedAt.length === 0)
|
|
356
|
+
return "updatedAt is required.";
|
|
357
|
+
try {
|
|
358
|
+
const trip = (0, store_1.readTripRecord)((0, identity_1.getAgentName)(), tripId);
|
|
359
|
+
const legIndex = trip.legs.findIndex((leg) => leg.legId === legId);
|
|
360
|
+
if (legIndex === -1)
|
|
361
|
+
return `leg ${legId} not found in trip ${tripId}.`;
|
|
362
|
+
const droppedLeg = trip.legs[legIndex];
|
|
363
|
+
const updated = {
|
|
364
|
+
...trip,
|
|
365
|
+
legs: [...trip.legs.slice(0, legIndex), ...trip.legs.slice(legIndex + 1)],
|
|
366
|
+
updatedAt,
|
|
367
|
+
};
|
|
368
|
+
(0, store_1.upsertTripRecord)((0, identity_1.getAgentName)(), updated);
|
|
369
|
+
(0, runtime_1.emitNervesEvent)({
|
|
370
|
+
component: "trips",
|
|
371
|
+
event: "trips.leg_removed",
|
|
372
|
+
message: "trip leg removed from ledger",
|
|
373
|
+
meta: {
|
|
374
|
+
agentId: (0, identity_1.getAgentName)(),
|
|
375
|
+
tripId,
|
|
376
|
+
legId,
|
|
377
|
+
kind: droppedLeg.kind,
|
|
378
|
+
/* v8 ignore next -- defensive: reason typing always string in normal call sites @preserve */
|
|
379
|
+
reason: typeof args.reason === "string" ? args.reason : undefined,
|
|
380
|
+
},
|
|
381
|
+
});
|
|
382
|
+
/* v8 ignore next -- pluralization branch: tests don't exhaustively cover both 1-leg and N-leg removal outcomes @preserve */
|
|
383
|
+
return `leg ${legId} removed from ${tripId}. trip now has ${updated.legs.length} leg${updated.legs.length === 1 ? "" : "s"}.`;
|
|
384
|
+
} /* v8 ignore start -- error-classification branches: TripNotFoundError vs unexpected store failure; the latter is covered by trip-store unit tests rather than tool-level fixtures @preserve */
|
|
385
|
+
catch (error) {
|
|
386
|
+
if (error instanceof store_1.TripNotFoundError)
|
|
387
|
+
return error.message;
|
|
388
|
+
return `remove failed: ${error instanceof Error ? error.message : String(error)}`;
|
|
389
|
+
} /* v8 ignore stop */
|
|
390
|
+
},
|
|
391
|
+
summaryKeys: ["tripId", "legId", "reason"],
|
|
392
|
+
},
|
|
327
393
|
{
|
|
328
394
|
tool: {
|
|
329
395
|
type: "function",
|