Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";

import { goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration } from "../src/data/capability-configuration.js";
import { goalStorageEditorValue, parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults } from "../src/data/capability-configuration.js";

const periodicReportEditor = {
fields: [
Expand All @@ -13,6 +13,22 @@ const periodicReportEditor = {
};

const storageEditor = { fields: [{ key: "new_goal_provider" }, { key: "canonical_creation" }, { key: "new_goal_handoff_mode" }] };
const cadenceEditor = { fields: [{ key: "count_unit" }, { key: "count" }, { key: "stride_strategy" }] };
const cadenceDefault = { count_unit: "effective_turns", count: 6, stride_strategy: "off" };
assert.deepEqual(projectEditableCapabilityConfiguration(cadenceEditor,
{ stride_strategy: "evidence_conditioned_v1" },
replanCadenceEditorDefaults({ count_unit: "effective_turns", count: 3, stride_strategy: "off" }, cadenceDefault)),
{ count_unit: "effective_turns", count: 3, stride_strategy: "evidence_conditioned_v1" },
"a sparse JSON draft returning to the form preserves the effective review interval");
assert.deepEqual(projectEditableCapabilityConfiguration(cadenceEditor,
{ count_unit: "completed_todos", count: 2 },
replanCadenceEditorDefaults({ count_unit: "effective_turns", count: 3, stride_strategy: "evidence_conditioned_v1" }, cadenceDefault)),
{ count_unit: "completed_todos", count: 2, stride_strategy: "off" },
"an explicit cadence wins and omitted strategy retains default-off semantics");
assert.deepEqual(replanCadenceEditorDefaults(
{ schema_version: "todo_replan_cadence_machine_defaults_v0", completed_todos: 4 }, cadenceDefault),
{ count_unit: "completed_todos", count: 4, stride_strategy: "off" },
"legacy effective intervals retain their own unit and count");
const storageDefault = { schema_version: "loopx_goal_storage_defaults_v1", new_goal_provider: "sqlite",
canonical_creation: true, new_goal_handoff_mode: "hard_lease" };
assert.deepEqual(projectEditableCapabilityConfiguration(storageEditor,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ export function replanCadenceEditorValue(value: unknown): unknown {
return { ...record, stride_strategy: record.stride_strategy ?? "off" };
}

/** Fill omitted review interval fields from the currently effective value. */
export function replanCadenceEditorDefaults(effective: unknown, fallback: unknown): Record<string, unknown> {
const defaults = { ...configurationObject(fallback) };
const live = configurationObject(replanCadenceEditorValue(effective));
if (Object.hasOwn(live, "count_unit")) defaults.count_unit = live.count_unit;
if (Object.hasOwn(live, "count")) defaults.count = live.count;
// Strategy omission keeps the existing default-off meaning.
return defaults;
}

/** A v0 storage selector did not enable creation; opening Settings must not. */
export function goalStorageEditorValue(value: unknown): unknown {
const record = configurationObject(value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
type GoalConfigurationPreview,
type GoalConfigurationPartialWrite,
} from "../../data/chat";
import { parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorValue } from "../../data/capability-configuration";
import { parseEditableCapabilityJson, projectEditableCapabilityConfiguration, replanCadenceEditorDefaults, replanCadenceEditorValue } from "../../data/capability-configuration";
import { useWorkspaceI18n } from "./i18n";
import { CapabilityConfigurationFields } from "./capability-configuration-fields";
import { withReportScheduleTimezone } from "./periodic-report-schedule-field";
Expand All @@ -37,7 +37,10 @@ type CapabilityMutationState = Readonly<{
}>;

function goalWriteDraft(selected: CapabilityConfigurationCatalog["capabilities"][number], draft: Record<string, unknown>) {
const writable = projectEditableCapabilityConfiguration(selected.configuration_editor, draft, selected.default);
const defaults = selected.capability_id === "todo_replan_cadence"
? replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
: selected.default;
const writable = projectEditableCapabilityConfiguration(selected.configuration_editor, draft, defaults);
if (selected.capability_id === "pull_request_review" && !Object.hasOwn(selected.current ?? {}, "owner_logins")
&& JSON.stringify(writable.owner_logins ?? []) === JSON.stringify(selected.effective_configuration?.configuration?.owner_logins ?? [])) {
delete writable.owner_logins;
Expand Down Expand Up @@ -148,7 +151,10 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{
setJsonDraft(text);
const parsed = parseEditableCapabilityJson(selected.configuration_editor, text);
setMutation((current) => ({ ...current, preview: null, draft: parsed
? projectEditableCapabilityConfiguration(selected.configuration_editor, parsed, selected.default)
? projectEditableCapabilityConfiguration(selected.configuration_editor, parsed,
selected.capability_id === "todo_replan_cadence"
? replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default)
: selected.default)
: current.draft }));
setError(null);
}
Expand All @@ -157,7 +163,12 @@ function useCapabilityMutation({ goalId, onApplied, selected, t }: Readonly<{
if (mutation.busy || !jsonValid) return;
if (editorMode === "guided") setJsonDraft(JSON.stringify(mutation.draft, null, 2));
setEditorMode(editorMode === "guided" ? "json" : "guided");
setMutation((current) => ({ ...current, preview: null }));
setMutation((current) => ({ ...current, preview: null,
draft: editorMode === "json" && selected?.capability_id === "todo_replan_cadence"
? projectEditableCapabilityConfiguration(selected.configuration_editor, current.draft,
replanCadenceEditorDefaults(selected.effective_configuration?.configuration, selected.default))
: current.draft,
}));
}

return { apply, changeDraft, changeJson, changeMode, editorMode, jsonDraft, jsonValid, error, mutation, preview };
Expand Down
Binary file added docs/assets/stride-effective-config-after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions examples/personal-workspace-browser/replan-cadence.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ export const replanCadenceScenario = {
return packet.capability_catalog.capabilities.find((row) => row.capability_id === "todo_replan_cadence");
}
if (await strategy.inputValue() !== "evidence_conditioned_v1") throw new Error("Goal did not inherit strategy version");
await page.getByRole("button", { name: "编辑 JSON", exact: true }).click();
await page.locator("#goal-configuration-json").fill(JSON.stringify({ stride_strategy: "evidence_conditioned_v1" }));
await page.getByRole("button", { name: "返回表单", exact: true }).click();
if (await unit.inputValue() !== "effective_turns" || await count.inputValue() !== "3") {
throw new Error("JSON-to-form conversion lost the effective review interval");
}
await page.getByRole("button", { name: "预览变更", exact: true }).click();
await applyGoalPreview();
const converted = await readGoalCadence();
if (converted.effective_configuration.configuration.count !== 3
|| converted.effective_configuration.configuration.count_unit !== "effective_turns") {
throw new Error("JSON-to-form save substituted the product-default interval");
}
await page.screenshot({ path: resolve(outputDir, "replan-cadence-json-form.png"), animations: "disabled" });
await count.fill("2");
await strategy.selectOption("off");
await page.getByRole("button", { name: "预览变更", exact: true }).click();
Expand Down
4 changes: 3 additions & 1 deletion loopx/cli_commands/goal_amendment_proposal.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,12 @@ def handle_goal_amendment_proposal_command(
if "operation_id" in parameters:
raise ValueError("supply operation_id once")
parameters["operation_id"] = args.operation_id
from ..capabilities.todo_replan_cadence.machine_defaults import resolve_todo_replan_cadence_goal
payload = execute_goal_amendment(registry_path=registry_path, goal_id=goal_id,
goal_instance_id=args.goal_instance_id, operation=args.governed,
actor_agent_id=args.agent_id, parameters=parameters,
runtime_root=runtime_root, execute=args.execute)
runtime_root=runtime_root, execute=args.execute,
replan_configuration_reader=resolve_todo_replan_cadence_goal)
accepted = payload.get("ok") is True
print_payload(payload, output_format(args), render_goal_amendment_proposal_markdown)
return 0 if accepted else 1
Expand Down
10 changes: 9 additions & 1 deletion loopx/control_plane/goals/goal_amendment_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from __future__ import annotations

import json
from collections.abc import Callable
from pathlib import Path
from typing import Any, Mapping

Expand Down Expand Up @@ -55,6 +56,7 @@ def _replan_readback(derived: Mapping[str, Any], inventory: list[dict[str, Any]]
def execute_goal_amendment(
*, registry_path: Path, goal_id: str, goal_instance_id: str,
operation: str, actor_agent_id: str | None,
replan_configuration_reader: Callable[[Mapping[str, Any], Path], Mapping[str, Any]],
parameters: Mapping[str, Any] | None = None,
runtime_root: Path | None = None, execute: bool = False,
) -> dict[str, Any]:
Expand Down Expand Up @@ -89,6 +91,7 @@ def execute_goal_amendment(
gate_path = source_turn_effect_gate_path(registry_path, goal_id)
gate = json.loads(gate_path.read_text()) if gate_path.exists() else None
agents = registered_agent_ids_for_goal(goal)
effective_replan_goal: Mapping[str, Any] | None = None
derived = _derive_open_replan_obligation_inventory(runtime_root=root, goal_id=goal_id,
goal=goal, state_text="", goal_ref=goal_ref)
obligations = _open_replan_obligation_inventory(goal_id=goal_id, registered_agents=agents, status_item=derived)
Expand All @@ -104,12 +107,17 @@ def execute_goal_amendment(
"runtime_root": str(root), "observed_at": now_utc_iso(), "source": source}

def inspect() -> dict[str, Any]:
nonlocal effective_replan_goal
value = effect_runtime_result("goal.amendment.execute", {**common, "operation": "inspect", "dry_run": True},
timeout=CANONICAL_AUTHORITY_WRITE_TIMEOUT_SECONDS)
if not isinstance(value, dict):
raise TypeError("Goal amendment readback is invalid")
# Resolve once for current review observations. Historical
# receipt lookup must not depend on current configuration IO.
if effective_replan_goal is None:
effective_replan_goal = replan_configuration_reader(goal, root)
value["replan_observations"] = _replan_readback(derived, obligations, value,
stride_strategy=(goal.get("execution_profile") or {}).get("stride_strategy", "off"))
stride_strategy=(effective_replan_goal.get("execution_profile") or {}).get("stride_strategy", "off"))
return value

if operation in {"propose", "commit", "claim"}:
Expand Down
10 changes: 7 additions & 3 deletions loopx/presentation/goal_amendment_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from ..chat import redact_local_paths
from ..feedback import validate_goal_id
from ..capabilities.todo_replan_cadence.machine_defaults import resolve_todo_replan_cadence_goal
from ..control_plane.goals.goal_amendment_runtime import execute_goal_amendment
from ..control_plane.goals.source_session_registry_state import current_goal_ref
from ..control_plane.projects.registry_codec import load_project_registry
Expand Down Expand Up @@ -83,10 +84,12 @@ def _amendment_inspect(self) -> None:
if len(query.get("operation_id", [])) != 1 or len(query.get("goal_instance_id", [])) != 1:
raise ValueError("Historical lookup requires one operation and exact Goal instance")
result = execute_goal_amendment(registry_path=source, goal_id=goal_id, goal_instance_id=query["goal_instance_id"][0],
operation="receipt", actor_agent_id=None, parameters={"receipt_operation_id": query["operation_id"][0]})
operation="receipt", actor_agent_id=None, parameters={"receipt_operation_id": query["operation_id"][0]},
replan_configuration_reader=resolve_todo_replan_cadence_goal)
else:
result = execute_goal_amendment(registry_path=source, goal_id=goal_ref["goal_id"],
goal_instance_id=goal_ref["goal_instance_id"], operation="inspect", actor_agent_id=None)
goal_instance_id=goal_ref["goal_instance_id"], operation="inspect", actor_agent_id=None,
replan_configuration_reader=resolve_todo_replan_cadence_goal)
self._send_json(_public(result))
except (OSError, TypeError, ValueError) as exc:
self._send_error(redact_local_paths(str(exc)), status=400, error_code="goal_amendment_unavailable")
Expand All @@ -104,7 +107,8 @@ def _amendment_configure(self) -> None:
parameters = body["parameters"]
if set(parameters) != {"permission_scope", "terminal_conditions", "policy", "expected_provider_revision"}:
raise ValueError("Only initial root policy fields are accepted here")
common = dict(registry_path=source, goal_id=goal_id, goal_instance_id=str(body["goal_instance_id"]), actor_agent_id=None)
common = dict(registry_path=source, goal_id=goal_id, goal_instance_id=str(body["goal_instance_id"]), actor_agent_id=None,
replan_configuration_reader=resolve_todo_replan_cadence_goal)
current = execute_goal_amendment(**common, operation="inspect")
if current.get("ok") is not True:
self._send_json(_public(current))
Expand Down
10 changes: 5 additions & 5 deletions loopx/semantics/project_registry_io_manifest_v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@
},
{
"site": "loopx/cli_commands/goal_amendment_proposal.py::<module>._resolve_goal_routing::codec_read:load_registry#1",
"line": 304,
"line": 306,
"column": 28,
"kind": "codec_read",
"api": "load_registry",
Expand Down Expand Up @@ -1455,15 +1455,15 @@
},
{
"site": "loopx/control_plane/goals/goal_amendment_runtime.py::<module>.execute_goal_amendment::codec_read:load_project_registry#1",
"line": 67,
"line": 69,
"column": 15,
"kind": "codec_read",
"api": "load_project_registry",
"classification": "codec_api"
},
{
"site": "loopx/control_plane/goals/goal_amendment_runtime.py::<module>.execute_goal_amendment::codec_transaction:source_session_registry_transaction#1",
"line": 77,
"line": 79,
"column": 14,
"kind": "codec_transaction",
"api": "source_session_registry_transaction",
Expand Down Expand Up @@ -2191,15 +2191,15 @@
},
{
"site": "loopx/presentation/goal_amendment_api.py::<module>.GoalAmendmentRequestMixin._amendment_inspect::codec_read:load_project_registry#1",
"line": 80,
"line": 81,
"column": 24,
"kind": "codec_read",
"api": "load_project_registry",
"classification": "codec_api"
},
{
"site": "loopx/presentation/goal_amendment_api.py::<module>._source_registry::codec_read:load_project_registry#1",
"line": 23,
"line": 24,
"column": 15,
"kind": "codec_read",
"api": "load_project_registry",
Expand Down
Loading
Loading