@ponythewhite/base-context 1.0.7 → 1.0.8
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.md +19 -0
- package/dist/base-context-runtime/pyproject.toml +1 -1
- package/dist/base-context-runtime/src/rlm/__init__.py +3 -0
- package/dist/base-context-runtime/src/rlm/harness.py +86 -48
- package/dist/base-context-runtime/test/test_harness.py +215 -140
- package/dist/base-context-runtime/uv.lock +1 -1
- package/dist/build-info.json +1 -1
- package/dist/bundle/amazon-bedrock.js +3 -3
- package/dist/bundle/{anthropic-UV5GZDGU.js → anthropic-5PSCZEER.js} +2 -7
- package/dist/bundle/{azure-openai-responses-WSNPXLYC.js → azure-openai-responses-AYTLJR33.js} +2 -2
- package/dist/bundle/{bundled-modules-PS66L3IT.js → bundled-modules-IGMJH2MU.js} +5 -5
- package/dist/bundle/{chunk-UPVDERMD.js → chunk-2IPDSTDR.js} +11 -9
- package/dist/bundle/chunk-D3KOF57W.js +22040 -0
- package/dist/bundle/{chunk-VCW3YDFF.js → chunk-EXEFSSF4.js} +1475 -562
- package/dist/bundle/{chunk-VSIYFWYI.js → chunk-KT2BNEPE.js} +21 -8
- package/dist/bundle/{chunk-KTNETTHI.js → chunk-Q3MBSBEB.js} +3 -101
- package/dist/bundle/{chunk-LFPWHI42.js → chunk-QKIFBA7N.js} +2 -2
- package/dist/bundle/{chunk-PFROC4EV.js → chunk-RVJVXZCS.js} +12 -9
- package/dist/bundle/{cli-main-MMHTK7EB.js → cli-main-BUK7QTHO.js} +4 -4
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/{google-T32FMD4G.js → google-HTMCUJ7X.js} +2 -2
- package/dist/bundle/{google-vertex-56KP6I3B.js → google-vertex-ZQO65AXF.js} +2 -2
- package/dist/bundle/{main-XAIVH4Y7.js → main-2FWHT2WZ.js} +5 -5
- package/dist/bundle/{mistral-WGBMYIVU.js → mistral-SB7LU56G.js} +1 -1
- package/dist/bundle/{openai-codex-responses-PIV2RQ3G.js → openai-codex-responses-5CEW4VN3.js} +53 -27
- package/dist/bundle/{openai-completions-64C3JPR4.js → openai-completions-BAZJQSS2.js} +1 -1
- package/dist/bundle/{openai-responses-UI7RSTDV.js → openai-responses-VHOE427P.js} +48 -5
- package/dist/core/agent-session.d.ts +6 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +177 -24
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/canonical-context.d.ts +8 -0
- package/dist/core/canonical-context.d.ts.map +1 -1
- package/dist/core/canonical-context.js +37 -5
- package/dist/core/canonical-context.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +2 -2
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +31 -13
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/context-epoch.d.ts +1 -1
- package/dist/core/context-epoch.d.ts.map +1 -1
- package/dist/core/context-epoch.js +2 -1
- package/dist/core/context-epoch.js.map +1 -1
- package/dist/core/extensions/types.d.ts +2 -2
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/history-index-worker.d.ts.map +1 -1
- package/dist/core/history-index-worker.js +29 -8
- package/dist/core/history-index-worker.js.map +1 -1
- package/dist/core/history-index.d.ts +11 -1
- package/dist/core/history-index.d.ts.map +1 -1
- package/dist/core/history-index.js +2 -1
- package/dist/core/history-index.js.map +1 -1
- package/dist/core/inference-coordinator.d.ts.map +1 -1
- package/dist/core/inference-coordinator.js +108 -13
- package/dist/core/inference-coordinator.js.map +1 -1
- package/dist/core/kernel/bootstrap.d.ts.map +1 -1
- package/dist/core/kernel/bootstrap.js +97 -107
- package/dist/core/kernel/bootstrap.js.map +1 -1
- package/dist/core/kernel/repl-manager.d.ts.map +1 -1
- package/dist/core/kernel/repl-manager.js +11 -0
- package/dist/core/kernel/repl-manager.js.map +1 -1
- package/dist/core/kernel/shared.d.ts +2 -0
- package/dist/core/kernel/shared.d.ts.map +1 -1
- package/dist/core/kernel/shared.js.map +1 -1
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +4 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/prompts/rlm.d.ts.map +1 -1
- package/dist/core/prompts/rlm.js +4 -2
- package/dist/core/prompts/rlm.js.map +1 -1
- package/dist/core/public-context.d.ts.map +1 -1
- package/dist/core/public-context.js +16 -0
- package/dist/core/public-context.js.map +1 -1
- package/dist/core/refinement/refinement.d.ts +19 -5
- package/dist/core/refinement/refinement.d.ts.map +1 -1
- package/dist/core/refinement/refinement.js +90 -35
- package/dist/core/refinement/refinement.js.map +1 -1
- package/dist/core/request-view-selection.d.ts +10 -2
- package/dist/core/request-view-selection.d.ts.map +1 -1
- package/dist/core/request-view-selection.js +235 -100
- package/dist/core/request-view-selection.js.map +1 -1
- package/dist/core/retained-tool-output.d.ts +28 -0
- package/dist/core/retained-tool-output.d.ts.map +1 -0
- package/dist/core/retained-tool-output.js +102 -0
- package/dist/core/retained-tool-output.js.map +1 -0
- package/dist/core/selective-recovery.d.ts +47 -3
- package/dist/core/selective-recovery.d.ts.map +1 -1
- package/dist/core/selective-recovery.js +428 -109
- package/dist/core/selective-recovery.js.map +1 -1
- package/dist/core/session-history-index.d.ts +3 -1
- package/dist/core/session-history-index.d.ts.map +1 -1
- package/dist/core/session-history-index.js +1 -1
- package/dist/core/session-history-index.js.map +1 -1
- package/dist/core/settings-manager.d.ts +5 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/system-prompt.d.ts +3 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/task-frame.d.ts +1 -1
- package/dist/core/task-frame.d.ts.map +1 -1
- package/dist/core/task-frame.js +21 -1
- package/dist/core/task-frame.js.map +1 -1
- package/dist/core/task-state-reader.d.ts +10 -2
- package/dist/core/task-state-reader.d.ts.map +1 -1
- package/dist/core/task-state-reader.js +75 -9
- package/dist/core/task-state-reader.js.map +1 -1
- package/dist/core/tools/ipython.d.ts +5 -0
- package/dist/core/tools/ipython.d.ts.map +1 -1
- package/dist/core/tools/ipython.js +59 -17
- package/dist/core/tools/ipython.js.map +1 -1
- package/dist/core/tools/prime-context.d.ts.map +1 -1
- package/dist/core/tools/prime-context.js +1 -1
- package/dist/core/tools/prime-context.js.map +1 -1
- package/dist/core/view-units.d.ts +4 -0
- package/dist/core/view-units.d.ts.map +1 -1
- package/dist/core/view-units.js +28 -19
- package/dist/core/view-units.js.map +1 -1
- package/dist/installer.mjs +98 -77
- package/dist/modes/interactive/components/refinement-outcome-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/refinement-outcome-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +5 -5
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/skills/skill-creator/SKILL.md +18 -1
- package/docs/settings.md +45 -0
- package/docs/skills.md +77 -2
- package/package.json +4 -4
- package/skills/skill-creator/SKILL.md +18 -1
- package/dist/bundle/chunk-W46VZFI6.js +0 -21681
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.8] - 2026-09-18
|
|
4
|
+
|
|
5
|
+
- Fixed harness-state saves to replace files atomically, preserve failed mutations, and report corrupt state without overwriting it.
|
|
6
|
+
- Changed Python kernel bootstrap to reuse package metadata and dependency planning within each startup operation.
|
|
7
|
+
- Fixed accumulated TaskFrame overflow by preparing one bounded base and requiring an acknowledged context epoch before sending it.
|
|
8
|
+
- Fixed literal public-history recovery to scan exact substrings and resume bounded searches against the original captured branch.
|
|
9
|
+
- Added decoded UTF-8 byte windows for public fields and retained tool output, with actual offsets and omission flags.
|
|
10
|
+
- Changed large IPython results to publish bounded error-first previews and finalized detail-channel excerpts. Full captured text is retained in session output logs and is available through source-qualified recovery references. Live output streaming and structured result fields are preserved; bytes discarded upstream remain unavailable.
|
|
11
|
+
- Fixed kernel capture completeness when output arrives after a stream reaches its exact output limit.
|
|
12
|
+
- Reused reduced task evidence for unchanged captured sources and process only appended evidence; rebuild on branch/source changes while retaining relation updates and complete-view budget checks.
|
|
13
|
+
- Reused prepared dependency closure data within request selection and prepare the fixed history INSERT once per SQLite connection.
|
|
14
|
+
- Allowed request-budget recovery to propose a fresh TaskFrame base without publishing it before the native epoch ACK.
|
|
15
|
+
- Kept session paths after stable harness guidance and flush completed/error stream presentation before shutdown.
|
|
16
|
+
- Changed automatic recovery to try bounded TaskFrame rebasing and legal public-view reduction before summary recovery; reject oversized mandatory context and size summaries against retained context and output allowance.
|
|
17
|
+
- Reused prepared history-index inserts and guide repeated Python work toward small editable helpers with fresh inputs.
|
|
18
|
+
- Fixed resuming overflowed sessions using canonical source order and restored assistant identities; filter failed replies before provider mapping and re-acknowledge unbudgeted summary checkpoints before sending.
|
|
19
|
+
- Skipped automatic refinement when task and goal content are unchanged, and kept refinement history and version-only bookkeeping out of the main prompt.
|
|
20
|
+
- Added opt-in scoped error-fix advice with explicit workspace memory CRUD and bounded deterministic retrieval.
|
|
21
|
+
|
|
3
22
|
## [1.0.7] - 2026-09-18
|
|
4
23
|
|
|
5
24
|
- Fixed reopening saved sessions with native tool continuation history.
|
|
@@ -89,6 +89,9 @@ async def prime_context(request: object) -> dict[str, object]:
|
|
|
89
89
|
attached intact to this active cell's finalized ipython tool result. The
|
|
90
90
|
returned value alone is not a model-context update. Freshness is unknown.
|
|
91
91
|
Detached requests and sessions without the native tool are not authorized.
|
|
92
|
+
Resume search/recover with its cursor until exhausted; cursors are ephemeral.
|
|
93
|
+
Read accepts one-based lines or zero-based, end-exclusive UTF-8 byte windows.
|
|
94
|
+
Returned offsets delimit complete decoded characters; omission flags mark gaps.
|
|
92
95
|
"""
|
|
93
96
|
return await host_request("prime_context", {"request": request})
|
|
94
97
|
|
|
@@ -10,16 +10,18 @@ Execution still belongs to Base Context's TypeScript host and the existing
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import json
|
|
13
|
+
import os
|
|
13
14
|
from dataclasses import asdict, dataclass, field, fields
|
|
14
15
|
from datetime import datetime, timezone
|
|
15
16
|
from os import fstat, linesep
|
|
16
17
|
from pathlib import Path
|
|
18
|
+
from tempfile import NamedTemporaryFile
|
|
17
19
|
from typing import Any, Literal
|
|
18
20
|
|
|
19
21
|
from .product import assert_product_state_path, product_env, product_state_path
|
|
20
22
|
|
|
21
23
|
HarnessKind = Literal["prompt", "memory", "skill", "subagent"]
|
|
22
|
-
HarnessScope = Literal["local", "global"]
|
|
24
|
+
HarnessScope = Literal["local", "global", "workspace"]
|
|
23
25
|
|
|
24
26
|
_DEFAULT_FILE_NAME = "harness_state.json"
|
|
25
27
|
_DEFAULT_HARNESS_DIR_NAME = "harness"
|
|
@@ -56,15 +58,12 @@ def _resolve_global_flag(global_: bool = False, extra: dict[str, Any] | None = N
|
|
|
56
58
|
return bool(global_)
|
|
57
59
|
|
|
58
60
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if sep and rest and scope in ("local", "global"):
|
|
66
|
-
return rest, global_ or scope == "global"
|
|
67
|
-
return id, global_
|
|
61
|
+
def _resolve_scope(global_: bool, scope: HarnessScope | None, default: HarnessScope = "local") -> HarnessScope:
|
|
62
|
+
if scope is not None and scope not in ("local", "global", "workspace"):
|
|
63
|
+
raise ValueError(f"unknown harness scope {scope!r}")
|
|
64
|
+
if global_ and scope not in (None, "global"):
|
|
65
|
+
raise ValueError("global_=True conflicts with the selected harness scope")
|
|
66
|
+
return scope or ("global" if global_ else default)
|
|
68
67
|
|
|
69
68
|
|
|
70
69
|
def _env_dir(suffix: str) -> str | None:
|
|
@@ -73,7 +72,16 @@ def _env_dir(suffix: str) -> str | None:
|
|
|
73
72
|
return value or None
|
|
74
73
|
|
|
75
74
|
|
|
76
|
-
def _state_file(
|
|
75
|
+
def _state_file(
|
|
76
|
+
state_dir: str | Path | None = None, *, global_: bool = False, scope: HarnessScope | None = None
|
|
77
|
+
) -> Path:
|
|
78
|
+
scope = _resolve_scope(global_, scope)
|
|
79
|
+
if scope == "workspace":
|
|
80
|
+
root = state_dir or _env_dir("WORKSPACE_HARNESS_STATE_DIR")
|
|
81
|
+
if root is None:
|
|
82
|
+
raise RuntimeError("Workspace harness state requires the owner's BASE_CONTEXT_WORKSPACE_HARNESS_STATE_DIR")
|
|
83
|
+
return assert_product_state_path(Path(root).expanduser() / "harness-state.json")
|
|
84
|
+
global_ = scope == "global"
|
|
77
85
|
root: str | Path | None = state_dir
|
|
78
86
|
if root is None:
|
|
79
87
|
root = _env_dir("GLOBAL_HARNESS_STATE_DIR") if global_ else _env_dir("HARNESS_STATE_DIR")
|
|
@@ -163,7 +171,7 @@ class HarnessState:
|
|
|
163
171
|
self.file_path = (
|
|
164
172
|
assert_product_state_path(file_path)
|
|
165
173
|
if file_path
|
|
166
|
-
else _state_file(
|
|
174
|
+
else _state_file(scope=scope)
|
|
167
175
|
)
|
|
168
176
|
self.scope: HarnessScope = scope
|
|
169
177
|
# When set, local mutations raise instead of vanishing into a volatile
|
|
@@ -251,13 +259,9 @@ class HarnessState:
|
|
|
251
259
|
if error is admission_error:
|
|
252
260
|
raise
|
|
253
261
|
raise admission_error from error
|
|
254
|
-
|
|
255
|
-
# refinement. Treat it as empty; the next save() rewrites it cleanly.
|
|
256
|
-
data = {}
|
|
257
|
-
# json.load returns non-dict types for valid JSON like `null`, `[]`, or a bare
|
|
258
|
-
# string; coerce those to an empty object before attribute access.
|
|
262
|
+
raise ValueError(f"Invalid harness state at {self.file_path}: {error}") from error
|
|
259
263
|
if not isinstance(data, dict):
|
|
260
|
-
|
|
264
|
+
raise ValueError(f"Invalid harness state at {self.file_path}: expected a JSON object")
|
|
261
265
|
# Refuse before replacing cached state; count candidates before normalization.
|
|
262
266
|
self._check_item_count(data.get("entries"), data.get("refinements"))
|
|
263
267
|
|
|
@@ -279,7 +283,7 @@ class HarnessState:
|
|
|
279
283
|
continue
|
|
280
284
|
if not isinstance(entry_data.get("path"), str):
|
|
281
285
|
entry_data["path"] = "general"
|
|
282
|
-
if entry_data.get("scope") not in ("local", "global"):
|
|
286
|
+
if entry_data.get("scope") not in ("local", "global", "workspace"):
|
|
283
287
|
entry_data["scope"] = self.scope
|
|
284
288
|
if not isinstance(entry_data.get("source"), str):
|
|
285
289
|
entry_data["source"] = "agent"
|
|
@@ -322,14 +326,35 @@ class HarnessState:
|
|
|
322
326
|
self._loaded_mtime = mtime
|
|
323
327
|
return self
|
|
324
328
|
|
|
325
|
-
def
|
|
326
|
-
|
|
329
|
+
def _target(self, global_: bool, extra: dict[str, Any] | None = None) -> "HarnessState | None":
|
|
330
|
+
extra = dict(extra or {})
|
|
331
|
+
scope = extra.pop("scope", None)
|
|
332
|
+
global_ = _resolve_global_flag(global_, extra)
|
|
333
|
+
scope = _resolve_scope(global_, scope, self.scope)
|
|
334
|
+
if scope == self.scope and not global_:
|
|
327
335
|
return None
|
|
328
|
-
target = get_harness_state(
|
|
336
|
+
target = get_harness_state(
|
|
337
|
+
state_dir=self._global_target_state_dir if scope == "global" else None, scope=scope
|
|
338
|
+
)
|
|
329
339
|
if self.file_path is not None and target.file_path == self.file_path and target.scope == self.scope:
|
|
330
340
|
return None
|
|
331
341
|
return target
|
|
332
342
|
|
|
343
|
+
def _entry_target(
|
|
344
|
+
self, id: str | None, global_: bool, extra: dict[str, Any]
|
|
345
|
+
) -> tuple[str | None, "HarnessState | None"]:
|
|
346
|
+
extra = dict(extra)
|
|
347
|
+
if isinstance(id, str):
|
|
348
|
+
scope, sep, rest = id.partition(":")
|
|
349
|
+
if sep and rest and scope in ("local", "global", "workspace"):
|
|
350
|
+
if extra.get("scope") not in (None, scope):
|
|
351
|
+
raise ValueError("entry id conflicts with the selected harness scope")
|
|
352
|
+
extra["scope"] = scope
|
|
353
|
+
if scope == "global":
|
|
354
|
+
global_ = True
|
|
355
|
+
id = rest
|
|
356
|
+
return id, self._target(global_, extra)
|
|
357
|
+
|
|
333
358
|
def save(self) -> "HarnessState":
|
|
334
359
|
if self.file_path is None:
|
|
335
360
|
# Volatile stores have no encoded image; their containers remain uncapped.
|
|
@@ -349,8 +374,17 @@ class HarnessState:
|
|
|
349
374
|
if len(image) > self._max_source_bytes:
|
|
350
375
|
raise HarnessStateLimitError(f"Harness state exceeds max_source_bytes={self._max_source_bytes}")
|
|
351
376
|
self.file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
352
|
-
|
|
353
|
-
|
|
377
|
+
temporary_path: Path | None = None
|
|
378
|
+
try:
|
|
379
|
+
with NamedTemporaryFile(
|
|
380
|
+
mode="wb", dir=self.file_path.parent, prefix=f".{self.file_path.name}.", suffix=".tmp", delete=False
|
|
381
|
+
) as temporary:
|
|
382
|
+
temporary_path = Path(temporary.name)
|
|
383
|
+
temporary.write(image)
|
|
384
|
+
os.replace(temporary_path, self.file_path)
|
|
385
|
+
finally:
|
|
386
|
+
if temporary_path is not None:
|
|
387
|
+
temporary_path.unlink(missing_ok=True)
|
|
354
388
|
self._loaded_mtime = self._disk_mtime()
|
|
355
389
|
return self
|
|
356
390
|
|
|
@@ -369,8 +403,8 @@ class HarnessState:
|
|
|
369
403
|
global_: bool = False,
|
|
370
404
|
**kwargs: Any,
|
|
371
405
|
) -> HarnessEntry:
|
|
372
|
-
id,
|
|
373
|
-
if target
|
|
406
|
+
id, target = self._entry_target(id, global_, kwargs)
|
|
407
|
+
if target is not None:
|
|
374
408
|
return target.upsert(
|
|
375
409
|
kind,
|
|
376
410
|
title,
|
|
@@ -454,7 +488,7 @@ class HarnessState:
|
|
|
454
488
|
self.entries[kind][entry_id] = entry
|
|
455
489
|
try:
|
|
456
490
|
self.save()
|
|
457
|
-
except
|
|
491
|
+
except BaseException:
|
|
458
492
|
if existing:
|
|
459
493
|
vars(existing).update(previous_fields)
|
|
460
494
|
else:
|
|
@@ -463,8 +497,8 @@ class HarnessState:
|
|
|
463
497
|
return entry
|
|
464
498
|
|
|
465
499
|
def get(self, kind: HarnessKind, id: str, *, global_: bool = False, **kwargs: Any) -> HarnessEntry | None:
|
|
466
|
-
id,
|
|
467
|
-
if target
|
|
500
|
+
id, target = self._entry_target(id, global_, kwargs)
|
|
501
|
+
if target is not None:
|
|
468
502
|
return target.get(kind, id)
|
|
469
503
|
self._sync_from_disk()
|
|
470
504
|
if kind not in self.entries:
|
|
@@ -472,8 +506,8 @@ class HarnessState:
|
|
|
472
506
|
return self.entries[kind].get(id)
|
|
473
507
|
|
|
474
508
|
def delete(self, kind: HarnessKind, id: str, *, global_: bool = False, **kwargs: Any) -> bool:
|
|
475
|
-
id,
|
|
476
|
-
if target
|
|
509
|
+
id, target = self._entry_target(id, global_, kwargs)
|
|
510
|
+
if target is not None:
|
|
477
511
|
return target.delete(kind, id)
|
|
478
512
|
self._ensure_local_writable()
|
|
479
513
|
self._sync_from_disk()
|
|
@@ -486,15 +520,15 @@ class HarnessState:
|
|
|
486
520
|
del records[id]
|
|
487
521
|
try:
|
|
488
522
|
self.save()
|
|
489
|
-
except
|
|
490
|
-
# Keep the original map object and insertion order
|
|
523
|
+
except BaseException:
|
|
524
|
+
# Keep the original map object and insertion order when saving fails.
|
|
491
525
|
records.clear()
|
|
492
526
|
records.update(previous_records)
|
|
493
527
|
raise
|
|
494
528
|
return True
|
|
495
529
|
|
|
496
530
|
def list(self, kind: HarnessKind | None = None, *, global_: bool = False, **kwargs: Any) -> list[HarnessEntry]:
|
|
497
|
-
if target := self.
|
|
531
|
+
if target := self._target(global_, kwargs):
|
|
498
532
|
return target.list(kind)
|
|
499
533
|
self._sync_from_disk()
|
|
500
534
|
kinds = [kind] if kind else list(_KINDS)
|
|
@@ -520,8 +554,8 @@ class HarnessState:
|
|
|
520
554
|
global_: bool = False,
|
|
521
555
|
**kwargs: Any,
|
|
522
556
|
) -> HarnessEntry:
|
|
523
|
-
id,
|
|
524
|
-
if target
|
|
557
|
+
id, target = self._entry_target(id, global_, kwargs)
|
|
558
|
+
if target is not None:
|
|
525
559
|
return target.create(
|
|
526
560
|
kind,
|
|
527
561
|
title,
|
|
@@ -567,8 +601,8 @@ class HarnessState:
|
|
|
567
601
|
global_: bool = False,
|
|
568
602
|
**kwargs: Any,
|
|
569
603
|
) -> HarnessEntry:
|
|
570
|
-
id,
|
|
571
|
-
if target
|
|
604
|
+
id, target = self._entry_target(id, global_, kwargs)
|
|
605
|
+
if target is not None:
|
|
572
606
|
return target.update(
|
|
573
607
|
kind,
|
|
574
608
|
id,
|
|
@@ -755,7 +789,7 @@ class HarnessState:
|
|
|
755
789
|
global_: bool = False,
|
|
756
790
|
**kwargs: Any,
|
|
757
791
|
) -> RefinementEvent:
|
|
758
|
-
if target := self.
|
|
792
|
+
if target := self._target(global_, kwargs):
|
|
759
793
|
return target.record_refinement(trigger, changes, evidence=evidence, outcome=outcome, id=id)
|
|
760
794
|
self._ensure_local_writable()
|
|
761
795
|
self._sync_from_disk()
|
|
@@ -771,7 +805,7 @@ class HarnessState:
|
|
|
771
805
|
self.refinements.append(event)
|
|
772
806
|
try:
|
|
773
807
|
self.save()
|
|
774
|
-
except
|
|
808
|
+
except BaseException:
|
|
775
809
|
self.refinements.pop()
|
|
776
810
|
raise
|
|
777
811
|
return event
|
|
@@ -794,7 +828,7 @@ class HarnessState:
|
|
|
794
828
|
return plan
|
|
795
829
|
|
|
796
830
|
def overview(self, *, max_entries_per_kind: int = 20, global_: bool = False, **kwargs: Any) -> str:
|
|
797
|
-
if target := self.
|
|
831
|
+
if target := self._target(global_, kwargs):
|
|
798
832
|
return target.overview(max_entries_per_kind=max_entries_per_kind)
|
|
799
833
|
self._sync_from_disk()
|
|
800
834
|
lines = [
|
|
@@ -843,7 +877,7 @@ class HarnessState:
|
|
|
843
877
|
return "\n".join(lines)
|
|
844
878
|
|
|
845
879
|
def snapshot(self, *, global_: bool = False, **kwargs: Any) -> dict[str, Any]:
|
|
846
|
-
if target := self.
|
|
880
|
+
if target := self._target(global_, kwargs):
|
|
847
881
|
return target.snapshot()
|
|
848
882
|
self._sync_from_disk()
|
|
849
883
|
return {
|
|
@@ -858,12 +892,16 @@ class HarnessState:
|
|
|
858
892
|
|
|
859
893
|
|
|
860
894
|
def get_harness_state(
|
|
861
|
-
state_dir: str | Path | None = None, *, global_: bool = False, **kwargs: Any
|
|
895
|
+
state_dir: str | Path | None = None, *, global_: bool = False, scope: HarnessScope | None = None, **kwargs: Any
|
|
862
896
|
) -> HarnessState:
|
|
863
|
-
"""Return
|
|
897
|
+
"""Return a store for an explicit scope; local remains the default.
|
|
898
|
+
|
|
899
|
+
Workspace paths come from the session owner, never the mutable REPL cwd.
|
|
900
|
+
Explicit CRUD is available independently of automatic learning settings.
|
|
901
|
+
"""
|
|
864
902
|
global_ = _resolve_global_flag(global_, kwargs)
|
|
865
|
-
|
|
866
|
-
|
|
903
|
+
scope = _resolve_scope(global_, scope)
|
|
904
|
+
file_path = _state_file(state_dir, scope=scope)
|
|
867
905
|
cache_key = (file_path, scope)
|
|
868
906
|
state = _state_cache.get(cache_key)
|
|
869
907
|
if state is None:
|
|
@@ -873,9 +911,9 @@ def get_harness_state(
|
|
|
873
911
|
# state_dir call aliases the same local file. An explicit dir that merely
|
|
874
912
|
# aliases the env resolution must not sandbox later global_=True writes
|
|
875
913
|
# either, so pin only when the explicit dir actually diverges.
|
|
876
|
-
if state_dir is not None:
|
|
914
|
+
if state_dir is not None and scope != "workspace":
|
|
877
915
|
try:
|
|
878
|
-
env_file: Path | None = _state_file(
|
|
916
|
+
env_file: Path | None = _state_file(scope=scope)
|
|
879
917
|
except RuntimeError:
|
|
880
918
|
env_file = None
|
|
881
919
|
if file_path != env_file:
|