diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinActionProvider.java index 715a17bf..e5754b65 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinActionProvider.java @@ -6,4 +6,9 @@ interface BuiltinActionProvider { List provideBuiltinActions(); String createActionKey(String actionName); + + default String param(String requirement, String type, String detail) { + return requirement + "|" + type + "|" + detail; + } + } diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCapabilityActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCapabilityActionProvider.java index 92dcd8e7..1b50e75d 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCapabilityActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCapabilityActionProvider.java @@ -49,11 +49,10 @@ class BuiltinCapabilityActionProvider implements BuiltinActionProvider { false, null, Map.of( - "unit_id", "The id of the memory unit that contains the target memory slice.", - "slice_id", "The id of the memory slice to recall into context." + "unit_id", param("required", "string", "Memory unit id that contains the target memory slice."), + "slice_id", param("required", "string", "Memory slice id to load into short-lived context.") ), - "Recall the target memory slice into context using its unit_id and slice_id. " + - "This action loads the slice's original conversation messages as a short-lived recalled memory context block.", + "Purpose: bring a known memory slice back into the agent's working context so later reasoning can use the original conversation messages. Use when: the exact memory unit and slice have already been identified. Input: memory slice id and memory unit id. Does not: search, rank, summarize, or infer memories. Returns: whether the slice was recalled and a short result message.", tags, Set.of(), Set.of(), @@ -125,10 +124,10 @@ class BuiltinCapabilityActionProvider implements BuiltinActionProvider { false, null, Map.of( - "input", "Input required to initiate an internal Agent Turn.", - "target", "The people expected to reply to this internal Agent Turn." + "input", param("required", "string", "Prompt content for the internal Agent Turn."), + "target", param("required", "string", "Target user expected to receive/respond to the turn.") ), - "Create an internal Agent Turn to resolve a task.", + "Purpose: initiate a self-dialogue or proactive dialogue by sending input to a target participant. Use when: the agent needs to start a new turn for reflection, clarification, coordination, or active communication. Input: input content and the user expected to respond. Returns: confirmation that the turn was initiated. Does not: wait for or include the target's answer.", tags, Set.of(), Set.of(), diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCommandActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCommandActionProvider.java index 0193e009..1ed64aad 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCommandActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinCommandActionProvider.java @@ -7,14 +7,14 @@ import work.slhaf.partner.core.action.runner.execution.CommandExecutionService; import work.slhaf.partner.core.action.runner.policy.ExecutionPolicyRegistry; import work.slhaf.partner.core.action.runner.policy.WrappedLaunchSpec; -import java.time.Instant; -import java.time.Duration; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.time.Duration; +import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Function; @@ -38,6 +38,22 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { private final ConcurrentHashMap commandHandles = new ConcurrentHashMap<>(); private final CommandExecutionService commandExecutionService = CommandExecutionService.INSTANCE; + private Map commandParams() { + Map params = new LinkedHashMap<>(); + params.put("arg", param("required", "string", "Command executable name or path.")); + params.put("arg1", param("optional", "string", "First command argument after the executable.")); + params.put("arg2", param("optional", "string", "Second command argument after the executable.")); + params.put("argN", param("optional", "string", "Additional command arguments after arg2. Use consecutive numeric keys such as arg3, arg4, arg5, ... when more arguments are needed.")); + return params; + } + + private Map commandSessionParams() { + Map params = new LinkedHashMap<>(); + params.put("desc", param("required", "string", "Short human-readable description of why this background command session exists.")); + params.putAll(commandParams()); + return params; + } + @Override public List provideBuiltinActions() { return List.of( @@ -61,8 +77,8 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { MetaActionInfo info = new MetaActionInfo( false, null, - Map.of("arg / argN", "Command arguments. Use arg for first argument, arg1/arg2... for remaining arguments."), - "Execute any allowed system commands and get result instantly, the number of arguments is not limited.", + commandParams(), + "Purpose: execute a short, non-interactive system command synchronously and return its completed output. Inputs: argv entries through arg/argN. Returns: JSON with result containing stdout/stderr/exit summary text. Use when: the task can finish quickly in one process call. Notes: do not use for long-running services, interactive programs, streaming output, or processes that need later inspection.", tags, Set.of(), Set.of(createActionKey("inspect")), @@ -92,11 +108,8 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { MetaActionInfo info = new MetaActionInfo( false, null, - Map.of( - "desc", "Command session description.", - "arg / argN", "Command arguments. Use arg for first argument, arg1/arg2... for remaining arguments." - ), - "Start a background command session and return execution id.", + commandSessionParams(), + "Purpose: start a long-running or streaming command as a background session. Inputs: session description and argument entries. Returns: JSON with executionId; pass it as id to inspect, read, or cancel. Use when: the command may run for a while, produce incremental output, or need later control. Notes: usually follow with builtin::command::inspect or builtin::command::read.", tags, Set.of(), Set.of(createActionKey("inspect")), @@ -145,8 +158,8 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { MetaActionInfo info = new MetaActionInfo( false, null, - Map.of("id", "Command session id."), - "Inspect a background command session.", + Map.of("id", param("required", "string", "Command executionId returned by builtin::command::start.")), + "Purpose: inspect status and short output summaries for an existing background command session. Inputs: id from command::start. Returns: JSON with executionId, desc, exitCode, stdoutSize, stderrSize, stdoutSummary, stderrSummary, startAt, and endAt. Use when: deciding whether a background command is still running, failed, or needs a full output read. Notes: use builtin::command::read for complete or paginated output.", tags, Set.of(createActionKey("overview")), Set.of(), @@ -195,12 +208,12 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { false, null, Map.of( - "id", "Command execution session id.", - "stream", "Target stream, stdout or stderr. Default stdout.", - "offset", "Read start offset. Default 0.", - "limit", "Read max length. Default 4096." + "id", param("required", "string", "Command executionId returned by builtin::command::start."), + "stream", param("optional", "string", "Target stream to read. Allowed values: stdout, stderr. Default: stdout."), + "offset", param("optional", "int", "Start byte offset in the persisted stream log. Default: 0. Use nextOffset from a previous read to continue."), + "limit", param("optional", "int", "Maximum bytes to read from the persisted stream log. Default: 4096. Must be greater than 0.") ), - "Read output from a background command session.", + "Purpose: read stdout or stderr content from a background command session with offset pagination. Returns: JSON with content, contentTruncated, nextOffset, and eof. Use when: inspect summaries are insufficient or a command failed and stderr/stdout details are needed. Notes: repeat with nextOffset until eof or enough evidence is collected.", tags, Set.of(createActionKey("overview")), Set.of(), @@ -263,8 +276,8 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { MetaActionInfo info = new MetaActionInfo( false, null, - Map.of("id", "Command session id."), - "Cancel a background command session.", + Map.of("id", param("required", "string", "Command executionId returned by builtin::command::start.")), + "Purpose: terminate an existing background command session. Inputs: id from command::start. Returns: JSON with ok and executionId. Use when: the user explicitly asks to stop a command, or correction determines the running command is wrong, stuck, unsafe, or no longer needed. Notes: this has process side effects and should not be used as a status check.", tags, Set.of(), Set.of(createActionKey("overview")), @@ -316,16 +329,16 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { false, null, Map.of(), - "List all background command sessions.", + "Purpose: list known background command sessions. Inputs: none. Returns: JSON with result array of session overviews containing executionId, desc, and exitCode. Use when: an id is missing or correction needs to discover active command sessions before inspect/read/cancel. Notes: this does not start, stop, or read a command.", tags, Set.of(createActionKey("start")), Set.of(createActionKey("inspect"), createActionKey("read"), createActionKey("cancel")), false, JSONObject.of( - "commands", "Array of command session overview items.", - "command.executionId", "Command execution session id for each overview item.", - "command.desc", "Command session description for each overview item.", - "command.exitCode", "Process exit code for each overview item. Null when still running." + "result", "Array of command session overview items.", + "result[].executionId", "Command execution session id for each overview item.", + "result[].desc", "Command session description for each overview item.", + "result[].exitCode", "Process exit code for each overview item. Null when still running." ) ); Function, String> invoker = params -> { @@ -492,30 +505,36 @@ class BuiltinCommandActionProvider implements BuiltinActionProvider { } private List requireCommandArguments(Map params) { - List> entries = params.entrySet().stream() + List> unexpectedArgs = params.entrySet().stream() .filter(entry -> entry.getKey().startsWith(COMMAND_ARG_PREFIX)) - .sorted(Comparator.comparingInt(entry -> commandArgIndex(entry.getKey()))) + .filter(entry -> !COMMAND_ARG_PREFIX.equals(entry.getKey()) && !isNumberedCommandArg(entry.getKey())) .toList(); - if (entries.isEmpty()) { - throw new IllegalArgumentException("缺少命令参数"); + if (!unexpectedArgs.isEmpty()) { + throw new IllegalArgumentException("非法命令参数: " + unexpectedArgs.getFirst().getKey()); } + List commands = new ArrayList<>(); - for (Map.Entry entry : entries) { - commands.add(BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, entry.getKey())); - } + commands.add(BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, COMMAND_ARG_PREFIX)); + params.entrySet().stream() + .filter(entry -> isNumberedCommandArg(entry.getKey())) + .sorted(Comparator.comparingInt(entry -> commandArgIndex(entry.getKey()))) + .forEach(entry -> commands.add(BuiltinActionRegistry.BuiltinActionDefinition.requireString(params, entry.getKey()))); return commands; } - private int commandArgIndex(String key) { + private boolean isNumberedCommandArg(String key) { + if (!key.startsWith(COMMAND_ARG_PREFIX) || COMMAND_ARG_PREFIX.equals(key)) { + return false; + } String suffix = key.substring(COMMAND_ARG_PREFIX.length()).trim(); if (suffix.isEmpty()) { - return 0; - } - try { - return Integer.parseInt(suffix); - } catch (NumberFormatException ignored) { - return Integer.MAX_VALUE; + return false; } + return suffix.chars().allMatch(Character::isDigit); + } + + private int commandArgIndex(String key) { + return Integer.parseInt(key.substring(COMMAND_ARG_PREFIX.length()).trim()); } private String bufferSnapshot(StringBuilder buffer) { diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinDynamicActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinDynamicActionProvider.java index bf00efc7..a2b7a242 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinDynamicActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinDynamicActionProvider.java @@ -43,13 +43,13 @@ class BuiltinDynamicActionProvider implements BuiltinActionProvider { false, null, Map.of( - "desc", "Human-readable description for the temporary action. Required because the generated action name is only a short id.", - "code", "Dynamic action source code content.", - "codeType", "Code extension, for example py/sh/js.", - "launcher", "Interpreter or launcher used for ORIGIN execution.", - "meta", "MetaActionInfo extra fields as JSON string. Available fields example: {\"io\":true,\"params\":{\"input\":\"user input\"},\"tags\":[\"dynamic\"],\"preActions\":[\"builtin::command::execute\"],\"postActions\":[\"builtin::dynamic::persist\"],\"strictDependencies\":false,\"responseSchema\":{\"result\":\"dynamic result\"}}" + "desc", param("required", "string", "Human-readable description for the generated temporary action. This becomes the new action's planning description."), + "code", param("required", "string", "Dynamic action source code content."), + "codeType", param("required", "string", "Source file extension without or with leading dot, for example py, sh, js, or .py."), + "launcher", param("required", "string", "Interpreter or launcher command used to run the generated code, for example python3, bash, or node."), + "meta", param("required", "json-string", "MetaActionInfo fields for the generated action. Supported fields: io, params, tags, preActions, postActions, strictDependencies, responseSchema.") ), - "Generate a temporary ORIGIN action from source code and return a temporary actionKey.", + "Purpose: generate and register a temporary script as an ORIGIN MetaAction from supplied source code. Inputs: desc, code, codeType, launcher, and meta info. Returns: JSON with ok and temporary actionKey. Use when: existing MetaActions cannot perform the task and a temporary code-backed action is needed. Notes: this registers executable code with cleanup TTL; the generated action is temporary until builtin::dynamic::persist is called.", basicTags, Set.of(), Set.of(createActionKey("persist")), @@ -110,8 +110,8 @@ class BuiltinDynamicActionProvider implements BuiltinActionProvider { MetaActionInfo info = new MetaActionInfo( false, null, - Map.of("actionKey", "Temporary ORIGIN actionKey returned by generate."), - "Persist a temporary ORIGIN action and cancel its cleanup task.", + Map.of("actionKey", param("required", "string", "Temporary ORIGIN actionKey returned by builtin::dynamic::generate.")), + "Purpose: persist a previously generated temporary ORIGIN MetaAction and cancel its cleanup task. Inputs: actionKey from dynamic::generate. Returns: JSON with ok and actionKey. Use when: a generated temporary action should become reusable beyond its cleanup TTL. Notes: this has persistence side effects and should only be used when the generated action is validated or explicitly requested.", basicTags, Set.of(createActionKey("generate")), Set.of(), diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java index 646a38e9..f7273955 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/builtin/BuiltinInterventionActionProvider.java @@ -56,9 +56,9 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { false, null, Map.of( - "actionId", "Uuid of the interrupted executable action to resume." + "actionId", param("required", "string", "Uuid of an executable action currently in INTERRUPTED status.") ), - "Resume action that is interrupted.", + "Purpose: resume an executable action that is currently interrupted. Inputs: actionId. Returns: plain text success or failure reason. Use when: an intervention or user decision is complete and the original action should continue. Notes: this does not modify the action chain; use create_intervention before resuming when chain changes are required.", tags, Set.of(), Set.of(), @@ -101,14 +101,14 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { true, null, Map.of( - "actionId", "Uuid of the executing action that should enter intervention flow.", - "actionInfo", "Readable summary of the current action, used to explain the intervention context to the target.", - "demand", "What feedback, decision or operation is required from the target user.", - "target", "Target user or channel identifier that should receive the intervention request.", - "input", "Prompt content used to initiate the intervention turn toward the target.", - "timeout", "Maximum wait time for the interruption result, in the unit expected by ExecutableAction.interrupt(timeout)." + "actionId", param("required", "string", "Uuid of the executing action that should be interrupted for external intervention."), + "actionInfo", param("required", "string", "Readable summary of the current action and why intervention is needed."), + "demand", param("required", "string", "Specific feedback, decision, permission, or operation required from the target."), + "target", param("required", "string", "Target user or channel identifier that should receive the intervention request."), + "input", param("required", "string", "Prompt content used to initiate the intervention turn toward the target."), + "timeout", param("required", "int", "Maximum wait time passed to ExecutableAction.interrupt(timeout).") ), - "Try to acquire the target user to intervene this Action.", + "Purpose: interrupt an executing action and ask a target participant/channel for intervention. Inputs: actionId, actionInfo, demand, target, input, timeout. Returns: plain text intervention status. Use when: execution cannot safely continue without user decision, missing information, permission, or manual operation. Notes: this creates communication side effects and temporarily interrupts the action.", tags, Set.of(), Set.of(), @@ -190,7 +190,7 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { false, null, Map.of(), - "List existing actions that can be intervened", + "Purpose: list executable actions that can be inspected for possible intervention. Inputs: none. Returns: JSON array with tendency, description, status, and uuid for each action. Use when: an action id is needed before acquire_intervention, create_intervention, or resume_interrupted_action. Notes: this only discovers actions; it does not modify them.", tags, Set.of(), Set.of(), @@ -237,7 +237,7 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { false, null, Map.of(), - "List available MetaActions.", + "Purpose: list currently available MetaActions that can be used in action chains or interventions. Inputs: none. Returns: JSON array with action key/location, description, tags, and params. Use when: correction needs to discover valid action_key values for APPEND, INSERT, DELETE, or REBUILD. Notes: this is a discovery action and does not execute those MetaActions.", tags, Set.of(), Set.of(), @@ -282,12 +282,12 @@ class BuiltinInterventionActionProvider implements BuiltinActionProvider { false, null, Map.of( - "id", "The uuid of the Action to be intervened on.", - "type", "Intervention type. Allowed values: APPEND, INSERT, DELETE, CANCEL.", - "order", "Action chain order/stage to apply the intervention on.", - "actions", "Comma-separated actionKey list to be inserted, appended, rebuilt or deleted. Example: \"builtin::command::execute, builtin::capability::show_memory_slices\"" + "id", param("required", "string", "Uuid of the executable action to modify."), + "type", param("required", "string", "Intervention type. Allowed values: APPEND, INSERT, DELETE, CANCEL, REBUILD."), + "order", param("required", "int", "Action chain order/stage where the intervention applies."), + "actions", param("conditional", "string", "Comma-separated actionKey list to insert, append, rebuild, or delete. Required except for CANCEL. Example: builtin::command::execute,builtin::command::read.") ), - "Used to create an Action Intervention and act on the specified Executable Action.", + "Purpose: apply a structural intervention to an executable action chain. Inputs: id, type, order, and conditional actions. Returns: JSON with ok and optional result. Use when: correction must append, insert, delete, cancel, or rebuild future action-chain steps. Notes: actions must be valid action_key values; CANCEL may omit actions; this has direct action-chain side effects and resumes the target action after applying changes.", basicTags, Set.of( createActionKey("list_available_meta_actions"), diff --git a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrector.java b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrector.java index 8d6bb963..9f868e9c 100644 --- a/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrector.java +++ b/Partner-Core/src/main/java/work/slhaf/partner/module/action/executor/ActionCorrector.java @@ -4,6 +4,7 @@ import kotlin.Unit; import org.jetbrains.annotations.NotNull; import org.w3c.dom.Document; import org.w3c.dom.Element; +import work.slhaf.partner.core.action.ActionCapability; import work.slhaf.partner.core.cognition.CognitionCapability; import work.slhaf.partner.core.cognition.ContextBlock; import work.slhaf.partner.framework.agent.factory.capability.annotation.InjectCapability; @@ -30,6 +31,7 @@ public class ActionCorrector extends AbstractAgentModule.Sub doExecute(CorrectorInput input) { @@ -121,6 +126,17 @@ public class ActionCorrector extends AbstractAgentModule.Sub { + appendTextElement(document, block, "meta_action_key", value.getKey()); + appendTextElement(document, block, "meta_action_description", value.getValue().getDescription()); + return Unit.INSTANCE; + } + ); } }.encodeToMessage(); }