mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 14:21:29 +08:00
162 lines
6.3 KiB
Python
162 lines
6.3 KiB
Python
"""Continue a completed Builder task without replacing its owned transcript.
|
|
|
|
Routing proposes only an edit plan or a fresh test form. It never approves a
|
|
mutation, dispatches a workflow, or reuses consent from the preceding task.
|
|
"""
|
|
|
|
import json
|
|
from dataclasses import replace
|
|
from typing import TYPE_CHECKING
|
|
|
|
from core.dify_builder.models import Action, DifyBuilderContext, EntryMode, Session, Turn
|
|
from core.dify_builder.state import PcState, is_terminal
|
|
from runtime_contracts.builder.copy_ids import BuilderCopyId
|
|
from runtime_contracts.builder.presentation import copy_text
|
|
from services.dify_builder.application.ports import ContinuationClassifyingAgent
|
|
from services.dify_builder.business_acceptance import clear_task_acceptance
|
|
|
|
if TYPE_CHECKING:
|
|
from services.dify_builder.application.runner import Env, StepResult
|
|
|
|
|
|
def active_entry_mode(session: Session, context: DifyBuilderContext) -> EntryMode:
|
|
"""The session's creation mode stays immutable; continuation mode is CAS state."""
|
|
try:
|
|
return EntryMode(context.active_entry_mode) if context.active_entry_mode else session.entry_mode
|
|
except ValueError:
|
|
return session.entry_mode
|
|
|
|
|
|
def prepare_continuation(
|
|
env: "Env",
|
|
turn: Turn,
|
|
session: Session,
|
|
context: DifyBuilderContext,
|
|
graph: dict,
|
|
text: str,
|
|
*,
|
|
graph_revision: str,
|
|
) -> "StepResult | None":
|
|
if (
|
|
not is_terminal(session.current_state)
|
|
or session.current_state == PcState.FAILED
|
|
or context.paused
|
|
or context.recovery_class
|
|
):
|
|
return None
|
|
if not isinstance(env.agent, ContinuationClassifyingAgent):
|
|
return None
|
|
intent = env.agent.continuation_intent(context, graph, text)
|
|
if intent not in {"edit", "test"}:
|
|
return None
|
|
|
|
from services.dify_builder.application.handlers_fix import (
|
|
append_assistant,
|
|
append_card,
|
|
mint_checkpoint,
|
|
saved_test_inputs,
|
|
start_schema,
|
|
testdata_form,
|
|
)
|
|
from services.dify_builder.application.runner import StepResult
|
|
|
|
assert turn.action is not None
|
|
turn_id = turn.action.payload["client_turn_id"]
|
|
if context.verify_run_id:
|
|
run = env.repo.get_run(context.verify_run_id)
|
|
if (
|
|
run.status not in {"succeeded", "failed"}
|
|
or run.verification is None
|
|
or run.verification.execution_evidence is None
|
|
or not run.verification.execution_evidence.sealed
|
|
or run.verification.execution_evidence.safety_outcome == "execution_evidence_unknown"
|
|
):
|
|
# Keep its identity/evidence. A new prompt is not reconciliation.
|
|
items = append_assistant(
|
|
env,
|
|
session,
|
|
context,
|
|
copy_text(BuilderCopyId.REPLIES_EXECUTION_OUTCOME_IS_UNKNOWN_RUN_AGAIN_BEFORE_PUBLISHING),
|
|
turn_id=turn_id,
|
|
)
|
|
return StepResult(next=session.current_state, context=context, items=items)
|
|
|
|
context.task_control["generation"] = int(context.task_control.get("generation", 0)) + 1
|
|
context.retained_test_input_ref = context.test_input_ref or context.retained_test_input_ref
|
|
context.confirmed_consent_id = ""
|
|
context.confirmed_command_id = ""
|
|
context.effect_confirmation = {}
|
|
context.effect_confirmation_form = {}
|
|
context.code_risk_assessments = {}
|
|
clear_task_acceptance(context)
|
|
context.staged_repair = []
|
|
context.change_set = None
|
|
context.repair_attempts = 0
|
|
context.last_repair_error = ""
|
|
context.last_repair_graph_revision = ""
|
|
context.last_edit_rejection = ""
|
|
context.unknown_outcome_count = 0
|
|
context.verify_run_id = ""
|
|
context.checkpoint_id = ""
|
|
context.checkpoint_seq = context.next_seq
|
|
# The model request cannot inherit execution approval. Inputs remain data,
|
|
# and the existing form owner re-reads the current schema and risk facts.
|
|
if intent == "test":
|
|
mode = active_entry_mode(session, context)
|
|
next_state = {EntryMode.EDIT: PcState.EDIT_AWAIT_TESTDATA, EntryMode.BUILD: PcState.BUILD_AWAIT_TESTDATA}.get(
|
|
mode, PcState.FIX_AWAIT_TESTDATA
|
|
)
|
|
# The existing durable checkpoint is also this task's undo/history
|
|
# boundary. Never inherit the preceding task's restore point.
|
|
context.checkpoint_seq = context.next_seq
|
|
mint_checkpoint(env, session, context, graph, graph_revision, next_state)
|
|
schema = start_schema(graph, env.dify.get_app_mode(session.app_id, turn.actor))
|
|
values = saved_test_inputs(env, context)
|
|
from services.dify_builder.test_input_continuity import reusable_values
|
|
|
|
values = reusable_values(schema, values)
|
|
card, reply = testdata_form(
|
|
env,
|
|
session,
|
|
context,
|
|
turn.actor,
|
|
schema,
|
|
values,
|
|
copy_text(BuilderCopyId.REPLIES_PROVIDE_TEST_INPUTS_OR_USE_MOCK_DATA_TO_RUN_VALIDATION),
|
|
)
|
|
items = [
|
|
*append_card(context, card),
|
|
*append_assistant(env, session, context, reply, cards=["form"], turn_id=turn_id),
|
|
]
|
|
return StepResult(next=next_state, context=context, items=items)
|
|
|
|
from services.dify_builder.application.handlers_edit import handle_capability_check
|
|
|
|
context.active_entry_mode = str(EntryMode.EDIT)
|
|
context.plan_items = []
|
|
context.resource_selection = {}
|
|
context.edit_target_node_ids = []
|
|
context.edit_rules = {}
|
|
contextual_request = json.dumps(
|
|
{"previous_goal": context.goal_text[:6000], "requirements": context.requirements, "latest_request": text},
|
|
ensure_ascii=False,
|
|
)
|
|
followup = replace(
|
|
turn,
|
|
action=Action(
|
|
kind="send_edit_goal",
|
|
payload={"text": contextual_request},
|
|
base_version=session.version,
|
|
command_id=turn.action.command_id,
|
|
),
|
|
)
|
|
edit_session = replace(session, current_state=PcState.EDIT_CAPABILITY_CHECK, entry_mode=EntryMode.EDIT)
|
|
result = handle_capability_check(env, followup, edit_session, context)
|
|
# A retry of the same message resolves to this durable response instead of
|
|
# classifying and preparing the followup twice.
|
|
for item in result.items:
|
|
if item.kind == "assistant_turn":
|
|
item.payload["turn_id"] = turn_id
|
|
break
|
|
return result
|