mini-code-cli 0.1.1.1__py3-none-any.whl → 0.1.1.2__py3-none-any.whl
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.
- mini_code_cli/agent_loop.py +197 -27
- mini_code_cli/ui.py +26 -0
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/METADATA +23 -1
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/RECORD +8 -8
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/WHEEL +0 -0
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/entry_points.txt +0 -0
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/licenses/LICENSE +0 -0
- {mini_code_cli-0.1.1.1.dist-info → mini_code_cli-0.1.1.2.dist-info}/top_level.txt +0 -0
mini_code_cli/agent_loop.py
CHANGED
|
@@ -267,6 +267,42 @@ def _compaction_trigger(messages, token_limit, reserved_output_tokens, current_t
|
|
|
267
267
|
return False, ""
|
|
268
268
|
|
|
269
269
|
|
|
270
|
+
def _in_flight_tail(messages):
|
|
271
|
+
"""Return the messages of the turn that is still in flight, or ``[]``.
|
|
272
|
+
|
|
273
|
+
Compaction rewrites the transcript, so whatever it drops is gone from the
|
|
274
|
+
conversation *and* from everything saved afterwards. The last turn is not
|
|
275
|
+
finished work, though: it is either the instruction being answered right now
|
|
276
|
+
(the transcript ends with a ``user`` message) or the tool exchange the model
|
|
277
|
+
is waiting on (it ends with a ``tool`` result). Either way it has to be
|
|
278
|
+
carried across verbatim, or the model answers the summary instead of the
|
|
279
|
+
user's request -- and the typed line never reaches the recorder, the new
|
|
280
|
+
segment or a later restore.
|
|
281
|
+
|
|
282
|
+
The suffix starts at the last ``user`` message, which always begins a
|
|
283
|
+
self-contained turn: every ``assistant`` tool call inside it is followed by
|
|
284
|
+
its ``tool`` results, so the slice is a valid transcript on its own.
|
|
285
|
+
|
|
286
|
+
``[]`` when the transcript already ends with an ``assistant`` reply: that
|
|
287
|
+
turn is answered, and re-sending it would ask the model to answer it twice.
|
|
288
|
+
"""
|
|
289
|
+
if not messages:
|
|
290
|
+
return []
|
|
291
|
+
last = messages[-1]
|
|
292
|
+
if not isinstance(last, dict) or last.get("role") == "assistant":
|
|
293
|
+
return []
|
|
294
|
+
for index in range(len(messages) - 1, -1, -1):
|
|
295
|
+
message = messages[index]
|
|
296
|
+
if isinstance(message, dict) and message.get("role") == "user":
|
|
297
|
+
return list(messages[index:])
|
|
298
|
+
return []
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def _plural(count, noun, suffix="s"):
|
|
302
|
+
"""``"1 message"`` / ``"4 messages"`` -- for messages the user reads."""
|
|
303
|
+
return f"{count} {noun}" + ("" if count == 1 else suffix)
|
|
304
|
+
|
|
305
|
+
|
|
270
306
|
def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats,
|
|
271
307
|
history_index=0, token_limit=None, reserved_output_tokens=0,
|
|
272
308
|
current_tokens=None, plan_hook=None, console=None):
|
|
@@ -279,7 +315,9 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
279
315
|
against ``token_limit``. Nothing else triggers compaction: in particular the
|
|
280
316
|
number of messages is not a criterion. Compaction is then done by asking the
|
|
281
317
|
LLM to summarize the history and replacing the old turns with that summary,
|
|
282
|
-
keeping the original system prompt as the first message
|
|
318
|
+
keeping the original system prompt as the first message *and the turn that is
|
|
319
|
+
still in flight* (see :func:`_in_flight_tail`) verbatim after it -- a summary
|
|
320
|
+
of the user's current request is not the request. The compaction call
|
|
283
321
|
itself is a single, tool-free request, so it cannot trigger further tool
|
|
284
322
|
calls or recurse into compaction.
|
|
285
323
|
|
|
@@ -289,10 +327,12 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
289
327
|
``plan_hook`` is an optional callable the caller supplies only when running
|
|
290
328
|
under plan mode (in practice ``AgentSession._handle_compaction``). After the
|
|
291
329
|
summary is built the hook re-injects the running goal + plan so the compacted
|
|
292
|
-
messages carry them forward. It receives the compacted list and
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
330
|
+
messages carry them forward. It receives the compacted list *and* the
|
|
331
|
+
in-flight tail (see below) and must update the compacted list in place,
|
|
332
|
+
because that list is what gets returned to the caller. Passing it in --
|
|
333
|
+
instead of referencing the owning session -- keeps ``maybe_compact`` a
|
|
334
|
+
small, module-level, network-free helper that the test-suite can import and
|
|
335
|
+
exercise directly.
|
|
296
336
|
"""
|
|
297
337
|
# ``console`` defaults to None so callers may omit it, but every branch below
|
|
298
338
|
# reports through a console. Falling back to the shared one keeps the
|
|
@@ -311,8 +351,20 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
311
351
|
# bare messages would crash them.
|
|
312
352
|
return messages, False, history_index
|
|
313
353
|
|
|
354
|
+
# The turn this step is answering -- the instruction just typed, or the tool
|
|
355
|
+
# exchange the model is waiting on -- is carried across the rollover (see
|
|
356
|
+
# :func:`_in_flight_tail`). It is computed *before* the slow summary call so
|
|
357
|
+
# the console can tell the user right away that nothing they just sent is at
|
|
358
|
+
# stake; a compaction note that stays silent about that is exactly what makes
|
|
359
|
+
# the rollover look like it might have eaten the message.
|
|
360
|
+
tail = _in_flight_tail(messages)
|
|
361
|
+
kept_note = (f" The current turn ({_plural(len(tail), 'message')}) is kept "
|
|
362
|
+
f"verbatim." if tail else "")
|
|
363
|
+
|
|
314
364
|
try:
|
|
315
|
-
console.note(
|
|
365
|
+
console.note(
|
|
366
|
+
f"Compacting the conversation: {_plural(len(messages), 'message')} "
|
|
367
|
+
f"-> a summary ({reason}).{kept_note}", "warn")
|
|
316
368
|
summary_prompt = prompts.compaction_prompt(messages)
|
|
317
369
|
|
|
318
370
|
# The summariser sometimes answers with nothing at all. An empty summary
|
|
@@ -324,7 +376,7 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
324
376
|
# the over-cap transcript was retried, and paid for, on every step.
|
|
325
377
|
summary = ""
|
|
326
378
|
for attempt in (1, 2):
|
|
327
|
-
console.start_thinking("compacting")
|
|
379
|
+
console.start_thinking("compacting history")
|
|
328
380
|
try:
|
|
329
381
|
reply, _, compact_usage, _ = utils.unpack_llm_result(
|
|
330
382
|
utils.call_openai_server(
|
|
@@ -347,9 +399,14 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
347
399
|
console.note("The summariser returned an empty summary; retrying once.", "warn")
|
|
348
400
|
|
|
349
401
|
if not summary:
|
|
402
|
+
# Say plainly what did *not* happen: nothing was dropped and the
|
|
403
|
+
# segment did not move -- only the shrink did, so the next request
|
|
404
|
+
# may still be too large.
|
|
350
405
|
console.note(
|
|
351
406
|
f"Could not compact history: the summariser returned an empty "
|
|
352
|
-
f"summary twice. Keeping all {len(messages)}
|
|
407
|
+
f"summary twice. Keeping all {_plural(len(messages), 'message')} "
|
|
408
|
+
f"and history_{history_index}.json as they are -- nothing was "
|
|
409
|
+
f"lost, but the next request may still be too large "
|
|
353
410
|
f"({reason}).", "warn")
|
|
354
411
|
return messages, False, history_index
|
|
355
412
|
|
|
@@ -362,22 +419,56 @@ def maybe_compact(messages, server_url, model, api_key, llm_timeout, token_stats
|
|
|
362
419
|
"content": f"Here is a summary of our earlier conversation so far:\n{summary}",
|
|
363
420
|
})
|
|
364
421
|
|
|
422
|
+
# The turn in flight has to survive the rollover. Compaction runs at the
|
|
423
|
+
# *start* of a step, so ``messages`` normally ends with the user message
|
|
424
|
+
# this step is meant to answer, or with the tool result the model is
|
|
425
|
+
# waiting on. Replacing the transcript with just (system, summary)
|
|
426
|
+
# therefore dropped the user's instruction on the floor: the model
|
|
427
|
+
# answered the summary -- "Understood. What would you like me to do?" --
|
|
428
|
+
# and the typed line never reached the new segment, the recorder or a
|
|
429
|
+
# later restore (it survived only inside the *closed* segment, which no
|
|
430
|
+
# recorder record points at any more). ``tail`` was computed before the
|
|
431
|
+
# summary call so the opening note could already promise this.
|
|
432
|
+
|
|
365
433
|
# In plan mode, re-inject the running plan + current goal so a
|
|
366
434
|
# compaction (which would otherwise drop them) keeps the model
|
|
367
435
|
# working against the goal it is still trying to finish. The hook is
|
|
368
436
|
# wired in only when the caller runs under plan mode; otherwise
|
|
369
|
-
# compaction degrades to a plain summary.
|
|
437
|
+
# compaction degrades to a plain summary. It is also handed the tail so
|
|
438
|
+
# it can restate the *real* instruction rather than the summary text.
|
|
370
439
|
if plan_hook is not None:
|
|
371
|
-
plan_hook(compacted)
|
|
372
|
-
|
|
373
|
-
|
|
440
|
+
plan_hook(compacted, tail)
|
|
441
|
+
|
|
442
|
+
# ... and only then the live turn, so the user's actual instruction
|
|
443
|
+
# reads as the most recent thing the model has to act on.
|
|
444
|
+
compacted.extend(tail)
|
|
445
|
+
|
|
446
|
+
# What the user is shown has to answer the three questions a rollover
|
|
447
|
+
# raises: *what* was folded away (the summary itself), *how much* (the
|
|
448
|
+
# closing segment's message count, and what the new one starts with),
|
|
449
|
+
# and *where it went* (which segment file was closed and which was
|
|
450
|
+
# started).
|
|
451
|
+
console.note(f"Summary preview: {utils.oneline(summary, 120)}", "warn")
|
|
452
|
+
kept = (f"summary + {_plural(len(tail), 'kept message')}" if tail
|
|
453
|
+
else "summary only")
|
|
454
|
+
# Phrased as "old segment -> new segment" rather than "N messages -> M
|
|
455
|
+
# messages": the replacement can be *longer* than what it replaced (a
|
|
456
|
+
# system prompt, the summary and the kept turn), and a bare pair of
|
|
457
|
+
# counts that sometimes grows reads like a bug.
|
|
458
|
+
console.note(
|
|
459
|
+
f"Compacted history_{history_index}.json "
|
|
460
|
+
f"({_plural(len(messages), 'message')}) into a summary; "
|
|
461
|
+
f"history_{history_index + 1}.json now starts with "
|
|
462
|
+
f"{_plural(len(compacted), 'message')} ({kept}).", "warn")
|
|
374
463
|
# A new segment (history_index + 1) is started on compaction: the
|
|
375
464
|
# compacted messages are its snapshot, and every later turn appends
|
|
376
465
|
# to it. The caller records a single high-level 'summary' turn for
|
|
377
466
|
# this index so the recorder stays flat across the rollover.
|
|
378
467
|
return compacted, True, history_index + 1
|
|
379
468
|
except Exception as e:
|
|
380
|
-
console.note(
|
|
469
|
+
console.note(
|
|
470
|
+
f"Failed to compact history: {e}. Keeping all "
|
|
471
|
+
f"{_plural(len(messages), 'message')} unchanged.", "error")
|
|
381
472
|
|
|
382
473
|
return messages, False, history_index
|
|
383
474
|
|
|
@@ -427,6 +518,11 @@ class AgentSession:
|
|
|
427
518
|
|
|
428
519
|
self.plan_mode = bool(getattr(self.args, "plan", False))
|
|
429
520
|
|
|
521
|
+
# The good-bye box is printed once, from shutdown(). Several exits
|
|
522
|
+
# funnel through there (quit, exit, EOF, a /quit command) and a repeated
|
|
523
|
+
# box would read like a second session had ended.
|
|
524
|
+
self._farewell_shown = False
|
|
525
|
+
|
|
430
526
|
# Token accounting. ``token_stats`` covers the *current history
|
|
431
527
|
# segment* and is reset when compaction rolls over to a new one;
|
|
432
528
|
# ``total_usage`` is the whole-session accumulator and never decreases.
|
|
@@ -1263,8 +1359,12 @@ class AgentSession:
|
|
|
1263
1359
|
return
|
|
1264
1360
|
except KeyboardInterrupt:
|
|
1265
1361
|
self.console.note("Interrupting: Mini Code Exiting...", "error")
|
|
1362
|
+
self.shutdown(farewell=False)
|
|
1363
|
+
return
|
|
1266
1364
|
except Exception as e:
|
|
1267
1365
|
self.console.note(f"Exception {e}. Exiting...", "error")
|
|
1366
|
+
self.shutdown(farewell=False)
|
|
1367
|
+
return
|
|
1268
1368
|
self.shutdown()
|
|
1269
1369
|
|
|
1270
1370
|
def _emit_tip(self):
|
|
@@ -1508,7 +1608,7 @@ class AgentSession:
|
|
|
1508
1608
|
return True
|
|
1509
1609
|
|
|
1510
1610
|
|
|
1511
|
-
def _handle_compaction(self, compacted):
|
|
1611
|
+
def _handle_compaction(self, compacted, tail=()):
|
|
1512
1612
|
"""Re-inject goal + plan into the compacted messages (plan mode).
|
|
1513
1613
|
|
|
1514
1614
|
Plan-mode counterpart to the plan handling inside :meth:`maybe_compact`,
|
|
@@ -1516,7 +1616,8 @@ class AgentSession:
|
|
|
1516
1616
|
a network-free module-level helper. The loop wires in this method only
|
|
1517
1617
|
when plan mode is on; it re-injects the goal + plan via
|
|
1518
1618
|
:meth:`_apply_plan_to_compacted`, which also rewrites the tracking files
|
|
1519
|
-
to match.
|
|
1619
|
+
to match. ``tail`` is the turn that is still in flight, which
|
|
1620
|
+
``maybe_compact`` appends after the hook's messages.
|
|
1520
1621
|
|
|
1521
1622
|
The hook must update ``compacted`` **in place**: ``maybe_compact``
|
|
1522
1623
|
returns *that* list to the caller, which then assigns it to
|
|
@@ -1524,9 +1625,9 @@ class AgentSession:
|
|
|
1524
1625
|
discarded a moment later, silently dropping the goal and plan -- which is
|
|
1525
1626
|
exactly what happened before this was a slice assignment.
|
|
1526
1627
|
"""
|
|
1527
|
-
compacted[:] = self._apply_plan_to_compacted(compacted)
|
|
1628
|
+
compacted[:] = self._apply_plan_to_compacted(compacted, tail)
|
|
1528
1629
|
|
|
1529
|
-
def _apply_plan_to_compacted(self, messages):
|
|
1630
|
+
def _apply_plan_to_compacted(self, messages, tail=()):
|
|
1530
1631
|
"""Re-inject the current goal + plan after a history compaction.
|
|
1531
1632
|
|
|
1532
1633
|
Compaction replaces the transcript with a summary, which would otherwise
|
|
@@ -1534,16 +1635,18 @@ class AgentSession:
|
|
|
1534
1635
|
back off disk and append them as fresh user turns so the model keeps
|
|
1535
1636
|
working against the goal it is still trying to finish. The goal is
|
|
1536
1637
|
appended last so it reads as the most recent instruction the model
|
|
1537
|
-
should act on
|
|
1638
|
+
should act on -- ``maybe_compact`` then appends the in-flight turn, which
|
|
1639
|
+
is the same instruction verbatim.
|
|
1538
1640
|
"""
|
|
1539
1641
|
plan_text = self.plan_manager.read_plan()
|
|
1540
1642
|
goal_text = self.plan_manager.read_goal()
|
|
1541
1643
|
|
|
1542
|
-
# Recover the user's latest instruction from the
|
|
1543
|
-
#
|
|
1544
|
-
#
|
|
1644
|
+
# Recover the user's latest instruction from the *in-flight turn* -- not
|
|
1645
|
+
# from ``messages``, which by now holds the summary -- so a session that
|
|
1646
|
+
# compacts before its goal was recorded still restates the real
|
|
1647
|
+
# instruction instead of the summary text.
|
|
1545
1648
|
current_input = None
|
|
1546
|
-
for m in reversed(
|
|
1649
|
+
for m in reversed(list(tail or ())):
|
|
1547
1650
|
if m.get("role") == "user":
|
|
1548
1651
|
current_input = m.get("content")
|
|
1549
1652
|
break
|
|
@@ -1931,8 +2034,15 @@ class AgentSession:
|
|
|
1931
2034
|
self.console.note(
|
|
1932
2035
|
f"Saved usage to: {usage_path} (session total: {total_path})", "info")
|
|
1933
2036
|
|
|
1934
|
-
def shutdown(self):
|
|
1935
|
-
"""Persist and print a final summary (called on quit / exit).
|
|
2037
|
+
def shutdown(self, farewell=True):
|
|
2038
|
+
"""Persist and print a final summary (called on quit / exit).
|
|
2039
|
+
|
|
2040
|
+
``farewell=False`` is used by the interrupt / fatal paths in :meth:`run`,
|
|
2041
|
+
which have already told the user (in red) that the session is being torn
|
|
2042
|
+
down: a cheerful "thanks and here is your state" box on top of a crash is
|
|
2043
|
+
the wrong note, and the state it would recap is the state that just
|
|
2044
|
+
failed.
|
|
2045
|
+
"""
|
|
1936
2046
|
self._persist()
|
|
1937
2047
|
segments = history_segment_indices(self.session_dir)
|
|
1938
2048
|
# List what is really on disk. The old message claimed
|
|
@@ -1948,7 +2058,62 @@ class AgentSession:
|
|
|
1948
2058
|
self.console.note(
|
|
1949
2059
|
f"Saved session to {self.cache_dir}/{self.unique_id}/ "
|
|
1950
2060
|
f"({present} + summary_history.json).", "ok")
|
|
1951
|
-
|
|
2061
|
+
# The detailed per-field usage block is verbose-only (the console
|
|
2062
|
+
# documents it that way); in the quiet default view the good-bye box
|
|
2063
|
+
# below carries the same totals, so nothing is lost by leaving it out.
|
|
2064
|
+
if self.console.verbose:
|
|
2065
|
+
self.console.usage_summary(self.total_usage)
|
|
2066
|
+
if farewell:
|
|
2067
|
+
self._farewell(segments)
|
|
2068
|
+
|
|
2069
|
+
def _farewell(self, segments=None):
|
|
2070
|
+
"""Print the good-bye box: version, sign-off and what the session left.
|
|
2071
|
+
|
|
2072
|
+
Printed once (see ``_farewell_shown``) and only on a normal exit. It
|
|
2073
|
+
restates the state in one glanceable place -- the same facts the user
|
|
2074
|
+
would otherwise have to piece together from the save note, ``/history``
|
|
2075
|
+
and the (verbose-only) usage readout, which in the quiet default view
|
|
2076
|
+
means they saw none of it.
|
|
2077
|
+
"""
|
|
2078
|
+
if self._farewell_shown:
|
|
2079
|
+
return
|
|
2080
|
+
self._farewell_shown = True
|
|
2081
|
+
from . import __version__ as version
|
|
2082
|
+
|
|
2083
|
+
if segments is None:
|
|
2084
|
+
segments = history_segment_indices(self.session_dir)
|
|
2085
|
+
if not segments:
|
|
2086
|
+
where = f"history_{self.history_index}.json"
|
|
2087
|
+
elif len(segments) == 1:
|
|
2088
|
+
where = f"history_{segments[0]}.json"
|
|
2089
|
+
else:
|
|
2090
|
+
where = f"history_{segments[0]}..{segments[-1]}.json"
|
|
2091
|
+
|
|
2092
|
+
turns = len(self.summary)
|
|
2093
|
+
usage = self.total_usage or {}
|
|
2094
|
+
|
|
2095
|
+
def count(key):
|
|
2096
|
+
# A display detail on the exit path must never raise: a server that
|
|
2097
|
+
# reported ``null`` usage leaves a None here, and crashing while
|
|
2098
|
+
# saying goodbye would be the worst possible time to fail.
|
|
2099
|
+
try:
|
|
2100
|
+
return int(usage.get(key) or 0)
|
|
2101
|
+
except (TypeError, ValueError):
|
|
2102
|
+
return 0
|
|
2103
|
+
|
|
2104
|
+
rows = [
|
|
2105
|
+
("session id", self.unique_id),
|
|
2106
|
+
("saved under", f"{self.cache_dir}/{self.unique_id}/"),
|
|
2107
|
+
("history", f"{where} (segment {self.history_index}), "
|
|
2108
|
+
f"{_plural(len(self.messages), 'message')}"),
|
|
2109
|
+
("conversation", _plural(turns, "recorded turn")),
|
|
2110
|
+
("tokens", f"{count('prompt_tokens'):,} in / "
|
|
2111
|
+
f"{count('completion_tokens'):,} out "
|
|
2112
|
+
f"({count('total_tokens'):,} total)"),
|
|
2113
|
+
("calls", f"{count('llm_calls'):,} LLM, "
|
|
2114
|
+
f"{count('tool_calls'):,} tool"),
|
|
2115
|
+
]
|
|
2116
|
+
self.console.farewell(version, rows)
|
|
1952
2117
|
|
|
1953
2118
|
# -- single LLM turn ---------------------------------------------------
|
|
1954
2119
|
|
|
@@ -2000,14 +2165,19 @@ class AgentSession:
|
|
|
2000
2165
|
self.history_index)
|
|
2001
2166
|
except Exception as e:
|
|
2002
2167
|
self.console.note(
|
|
2003
|
-
f"Could not save the closed history segment
|
|
2168
|
+
f"Could not save the closed history segment "
|
|
2169
|
+
f"history_{self.history_index}.json: {e}. The compacted "
|
|
2170
|
+
f"conversation is unaffected, but /reset back into that "
|
|
2171
|
+
f"segment may be incomplete.", "warn")
|
|
2004
2172
|
self.history_index = new_index
|
|
2005
2173
|
self.token_stats = empty_usage()
|
|
2006
2174
|
# Snapshot the compacted messages into the new segment so its turns
|
|
2007
2175
|
# are preserved, and record the summary as the first turn of it.
|
|
2008
2176
|
save_history(self.messages, self.cache_dir, self.unique_id, self.history_index)
|
|
2009
2177
|
self._record_turn(
|
|
2010
|
-
f"summary:
|
|
2178
|
+
f"summary: history_{new_index - 1}.json "
|
|
2179
|
+
f"({_plural(len(closing_messages), 'message')}) compacted into a "
|
|
2180
|
+
f"summary; history_{self.history_index}.json started",
|
|
2011
2181
|
self.messages)
|
|
2012
2182
|
|
|
2013
2183
|
# Show a "… thinking" spinner while the LLM works (in-place, non-scrolling;
|
mini_code_cli/ui.py
CHANGED
|
@@ -739,6 +739,32 @@ class Console:
|
|
|
739
739
|
]
|
|
740
740
|
self.banner(f"MiniCodeCLI v{version}", lines, fg="cyan")
|
|
741
741
|
|
|
742
|
+
def farewell(self, version, rows, sign_off=None):
|
|
743
|
+
"""Render the exit banner: a sign-off plus a summary of the session state.
|
|
744
|
+
|
|
745
|
+
Deliberately narrower than :meth:`welcome`. Everything the welcome banner
|
|
746
|
+
shows is about how the session *will* run -- server, model, permissions,
|
|
747
|
+
allowed dir -- and all of it is irrelevant once the session is over. What
|
|
748
|
+
the user can still act on is what the session *left behind*: which
|
|
749
|
+
folder it was saved in, which history segments exist, how much was said
|
|
750
|
+
and what it cost. So the box carries only the version, a good-bye line
|
|
751
|
+
and those ``rows``.
|
|
752
|
+
|
|
753
|
+
``rows`` is a list of ``(label, value)`` pairs, laid out in a single
|
|
754
|
+
colon-aligned column like the welcome banner; the caller supplies the
|
|
755
|
+
pairs because it owns the state (the console stays free of session
|
|
756
|
+
policy).
|
|
757
|
+
"""
|
|
758
|
+
label_w = max((len(str(label)) for label, _ in rows), default=0)
|
|
759
|
+
lines = [sign_off or "Goodbye, and thanks for using MiniCodeCLI!", ""]
|
|
760
|
+
lines += [f"{str(label).ljust(label_w)} : {value}" for label, value in rows]
|
|
761
|
+
# Yellow, not the welcome banner's cyan: this is the one box that
|
|
762
|
+
# *closes* the session, so it reads as a distinct, valedictory note
|
|
763
|
+
# rather than the startup banner reappearing. Colours only apply on a
|
|
764
|
+
# colour-capable TTY (see :meth:`box`), so piped/ASCII output is
|
|
765
|
+
# unchanged.
|
|
766
|
+
self.banner(f"MiniCodeCLI v{version} - goodbye", lines, fg="yellow")
|
|
767
|
+
|
|
742
768
|
def announce_api_key(self, present):
|
|
743
769
|
if present:
|
|
744
770
|
self.note("API key found (MINI_CODE_API_KEY).", "ok")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mini-code-cli
|
|
3
|
-
Version: 0.1.1.
|
|
3
|
+
Version: 0.1.1.2
|
|
4
4
|
Summary: Mini Coding Agent. Allows you to easily experiment with a small coding agent.
|
|
5
5
|
Author-email: Nikolai Rozanov <nikolai.rozanov@gmail.com>
|
|
6
6
|
License: GNU AFFERO GENERAL PUBLIC LICENSE
|
|
@@ -970,6 +970,28 @@ reserve (`--max-tokens`). Unset (default) it is derived from the model's context
|
|
|
970
970
|
output reserve and a safety margin. Lower it to compact sooner, raise it to compact later — there
|
|
971
971
|
is no message-count cap to configure.
|
|
972
972
|
|
|
973
|
+
Compaction replaces the older turns with a summary, but it always carries the **turn in flight**
|
|
974
|
+
across the rollover: the message you just typed (or, mid-turn, the tool call the model is
|
|
975
|
+
waiting on) is kept verbatim after the summary. So a compaction that fires just as you send an
|
|
976
|
+
instruction cannot swallow it — the model still answers what you asked, and the turn stays in
|
|
977
|
+
the session file, in `/history` and in a later restore.
|
|
978
|
+
|
|
979
|
+
Because a rollover leaves no trace in the transcript (the old turns really are gone), the
|
|
980
|
+
console says in plain terms what happened — all of it without `--verbose`:
|
|
981
|
+
|
|
982
|
+
```
|
|
983
|
+
[!] Compacting the conversation: 42 messages -> a summary (51000 tokens in, 8192 reserved for the reply > 52000 budget). The current turn (1 message) is kept verbatim.
|
|
984
|
+
[!] Summary preview: The user asked for a token-based compaction trigger, then fixed ...
|
|
985
|
+
[!] Compacted history_0.json (42 messages) into a summary; history_1.json now starts with 3 messages (summary + 1 kept message).
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
So you can see *what* was folded away (a preview of the summary itself), *how much* (the
|
|
989
|
+
message count of the segment that was closed), *where it went* (`history_0.json` closed,
|
|
990
|
+
`history_1.json` started) and that nothing you just typed was lost. If the summariser answers
|
|
991
|
+
with an empty summary twice, or the call fails, compaction is abandoned and the note says so
|
|
992
|
+
explicitly — "nothing was lost, but the next request may still be too large" — with the
|
|
993
|
+
transcript and the segment left untouched.
|
|
994
|
+
|
|
973
995
|
|
|
974
996
|
## Serving a model locally
|
|
975
997
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
mini_code_cli/__init__.py,sha256=8J8l-IE6HHp-ib_LWbxbj4Hw7cH_7YWMa16j-aI9W7g,1420
|
|
2
|
-
mini_code_cli/agent_loop.py,sha256=
|
|
2
|
+
mini_code_cli/agent_loop.py,sha256=HhQmZ5QbE1pqTPHDKbe7GjTGMS8vbtqA2wd52gNqx4k,115180
|
|
3
3
|
mini_code_cli/config.py,sha256=TnvEkD31m9g3Zly8Q8TU9xCWE_Nx7He0FGo5F7IrDgE,9560
|
|
4
4
|
mini_code_cli/permissions.py,sha256=uQpQlUtQyVICpjUqdh9QQ_mgl51hZrGLHJzQR9vG124,2017
|
|
5
5
|
mini_code_cli/persistence.py,sha256=35rUqOvh0RKFgHoNzDghdXlbDofRxeK_t7M2_JuHcXc,22287
|
|
@@ -11,14 +11,14 @@ mini_code_cli/shell_safety_utils.py,sha256=5E126vGnG7iIuTZe_R5JrxB_zxZpMKGe_pilz
|
|
|
11
11
|
mini_code_cli/skills.py,sha256=Y6gltBE6dcOgRIBolOVUXAQYuhXbUn9heOSj9f0tz_Q,17717
|
|
12
12
|
mini_code_cli/tokens.py,sha256=eMA42Lazy1ng0OOB0KgvSHHWgxDjGkLvF2qUVllky80,5236
|
|
13
13
|
mini_code_cli/tools.py,sha256=dypVWHnNII6pIyf90H_mcLTqNiJPdW7cFex3n6xXw_I,27043
|
|
14
|
-
mini_code_cli/ui.py,sha256=
|
|
14
|
+
mini_code_cli/ui.py,sha256=89YAADOqTOM6ReFQbKo7hGNppgdzmuf5yKgJY5Obomc,40390
|
|
15
15
|
mini_code_cli/utils.py,sha256=LCzwq4_BSjWvLv-A0Z9_RXEwBt8lHWoJHMJs0Pf6miQ,8880
|
|
16
16
|
mini_code_cli/subagents/__init__.py,sha256=3fgcwY0pNq5iW29OxLPtSA3H5IKXhl2LUhwbBjLUDuU,1884
|
|
17
17
|
mini_code_cli/subagents/base.py,sha256=z3Zl8ABLXFOnEKCV9SgripsegVP1Go95z8OW2O_VstM,16259
|
|
18
18
|
mini_code_cli/subagents/builtin.py,sha256=knpQ1-U_Lp55HqUYPziMg4mca-F7b8v7z-VN9qU16CE,8038
|
|
19
|
-
mini_code_cli-0.1.1.
|
|
20
|
-
mini_code_cli-0.1.1.
|
|
21
|
-
mini_code_cli-0.1.1.
|
|
22
|
-
mini_code_cli-0.1.1.
|
|
23
|
-
mini_code_cli-0.1.1.
|
|
24
|
-
mini_code_cli-0.1.1.
|
|
19
|
+
mini_code_cli-0.1.1.2.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
|
|
20
|
+
mini_code_cli-0.1.1.2.dist-info/METADATA,sha256=aJ53M27Ss7cP_wQ2yB4JFAGD2sT9GbQKe8yiaI5BmcA,58131
|
|
21
|
+
mini_code_cli-0.1.1.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
22
|
+
mini_code_cli-0.1.1.2.dist-info/entry_points.txt,sha256=MwDp5UbXpI7wodSmOSYL8_nKutnBiyp96ZDDtaX7x14,60
|
|
23
|
+
mini_code_cli-0.1.1.2.dist-info/top_level.txt,sha256=3Hb6PpNkJYXvzDKXuuFJRvzCdsTRh0TuRWz-ScJ6qV0,14
|
|
24
|
+
mini_code_cli-0.1.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|