@agimon-ai/po-workflow 0.0.0-alpha.66 → 0.0.0-alpha.68

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.
@@ -409,7 +409,7 @@ function runBelongsToSession(run, sessionId) {
409
409
  }
410
410
  /** Whether the session handed this run to a workflow session of its own: it lists the run but does not own it. */
411
411
  function runLaunchedBySession(run, sessionId) {
412
- return run.launcherSessionId !== void 0 && run.launcherSessionId === sessionId && !runBelongsToSession(run, sessionId);
412
+ return run.piSessionId !== void 0 && run.launcherSessionId !== void 0 && run.launcherSessionId === sessionId && !runBelongsToSession(run, sessionId);
413
413
  }
414
414
  function parseTime(value) {
415
415
  if (value === void 0) return 0;
@@ -2912,6 +2912,155 @@ const WORKFLOW_API_BASE_PATH = "workflow";
2912
2912
  /** The SSE event name the screen stream writes. */
2913
2913
  const WORKFLOW_SCREEN_EVENT = "screen";
2914
2914
  //#endregion
2915
+ //#region src/services/piToolBridge/index.ts
2916
+ /**
2917
+ * Environment variable stamped onto every run launched from a Pi session.
2918
+ *
2919
+ * `RunWorkflowService` forwards `env` to the inner CLI invocation across the
2920
+ * `launch-command` delegation boundary and persists it on the run record, so
2921
+ * this survives backgrounding and identifies the launching session afterwards.
2922
+ */
2923
+ const PO_SESSION_ENV = "PO_SESSION_ID";
2924
+ /** Operator override for the per-session launch ceiling. */
2925
+ const MAX_CONCURRENT_ENV = "WORKFLOW_MCP_MAX_CONCURRENT";
2926
+ const DEFAULT_MAX_CONCURRENT = 5;
2927
+ /** Resolve the per-session launch ceiling from the environment. */
2928
+ function resolveMaxConcurrent(env) {
2929
+ const parsed = Number(env[MAX_CONCURRENT_ENV]);
2930
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT;
2931
+ }
2932
+ /**
2933
+ * True when this session launched the run.
2934
+ *
2935
+ * Fails closed on a missing session id: an unstamped record (a CLI launch) and
2936
+ * a call arriving before the session id is known both match nothing, so no run
2937
+ * ever leaks across sessions through an undefined-equals-undefined comparison.
2938
+ */
2939
+ function isSessionRun(record, sessionId) {
2940
+ return sessionId !== void 0 && record.env?.["PO_SESSION_ID"] === sessionId;
2941
+ }
2942
+ /** True when this session handed the run to a workflow session of its own: it is told, but does not own it. */
2943
+ function isLaunchedRun(record, sessionId) {
2944
+ const owner = record.env?.[PO_SESSION_ENV];
2945
+ return sessionId !== void 0 && typeof owner === "string" && owner !== "" && record.env?.["PO_WORKFLOW_LAUNCHER_SESSION_ID"] === sessionId && !isSessionRun(record, sessionId);
2946
+ }
2947
+ /** The line a launcher's finish notice adds: where the run lived, and where to take it up. */
2948
+ function launchedRunHint(record) {
2949
+ return `It ran in workflow session ${record.env?.["PO_SESSION_ID"] ?? "unknown"}; troubleshoot and recover it there.`;
2950
+ }
2951
+ /**
2952
+ * True when this session owns a run another session handed to it: it is the
2953
+ * run's workflow session, so a finished run is posted without starting a turn.
2954
+ */
2955
+ function isOwnedDelegatedRun(record, sessionId) {
2956
+ const launcher = record.env?.[WORKFLOW_LAUNCHER_SESSION_ENV];
2957
+ return isSessionRun(record, sessionId) && typeof launcher === "string" && launcher !== "" && launcher !== sessionId;
2958
+ }
2959
+ /**
2960
+ * What the caller is told when the registry, not the launcher, acknowledged a
2961
+ * launch.
2962
+ *
2963
+ * Leads with the run key because it is the handle every other workflow action
2964
+ * takes, and it is no longer available any other way: the launcher's own output
2965
+ * is not waited for, so nothing else in this result carries it.
2966
+ */
2967
+ function launchedRunSummary(record) {
2968
+ return [
2969
+ `Started ${record.displayName} in workspace ${record.workspace}.`,
2970
+ `Run key: ${record.runKey}`,
2971
+ "The run is registered and going. This call returned at that point rather than waiting for the launcher process to exit."
2972
+ ].join("\n");
2973
+ }
2974
+ /**
2975
+ * What the caller is told when the launch was handed off but no run has
2976
+ * registered inside the acknowledgement budget.
2977
+ *
2978
+ * Deliberately not an error: the launcher is slow rather than broken often
2979
+ * enough that failing here would strand runs that go on to start normally.
2980
+ * There is no run key to offer yet, so it points at the surfaces that do not
2981
+ * need one.
2982
+ */
2983
+ function launchHandoffSummary(workflowPath) {
2984
+ return [
2985
+ `Launch handed off for ${workflowPath}, but no run has registered yet.`,
2986
+ "Nothing was cancelled: the launcher is still starting, and a run that registers later still reports back here when it finishes.",
2987
+ "Do not launch it again. Ask the user to check the active workflow list if they need to see it sooner."
2988
+ ].join("\n");
2989
+ }
2990
+ new RegExp([
2991
+ "\\u001B\\[[0-9;?]*[ -/]*[@-~]",
2992
+ "\\u001B\\][^\\u0007\\u001B]*(?:\\u0007|\\u001B\\\\)",
2993
+ "\\u001B[@-Z\\\\-_]"
2994
+ ].join("|"), "g");
2995
+ /** Flatten MCP tool content to text for Pi. */
2996
+ function toolResultText(result) {
2997
+ return result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n").trim();
2998
+ }
2999
+ /**
3000
+ * Attach a short menu of next steps to a problem.
3001
+ *
3002
+ * An error with no route forward is where an agent invents one: retrying a
3003
+ * capacity limit, relaunching a run that should be recovered, or silently
3004
+ * giving up on work the user is waiting for. Naming two or three real options
3005
+ * turns an error into a decision, and the decision belongs to the user.
3006
+ */
3007
+ function withOptions(problem, options) {
3008
+ if (options.length === 0) return problem;
3009
+ return [
3010
+ problem,
3011
+ "",
3012
+ "Options, put these to the user rather than picking one yourself:",
3013
+ ...options.map(bullet)
3014
+ ].join("\n");
3015
+ }
3016
+ function bullet(option) {
3017
+ return `- ${option}`;
3018
+ }
3019
+ /** The job and step a run died on, preferring the progress log to the record. */
3020
+ function failurePosition(record, jobs) {
3021
+ const failedJob = jobs.find((job) => job.status === "failed");
3022
+ const failedStep = failedJob?.steps.find((step) => step.status === "failed");
3023
+ return {
3024
+ job: failedJob?.name ?? record.failedJob,
3025
+ step: failedStep?.name,
3026
+ reason: failedStep?.reason ?? failedJob?.reason
3027
+ };
3028
+ }
3029
+ /**
3030
+ * Describe a finished run for the agent that launched it.
3031
+ *
3032
+ * Carries the job identifiers back so a dispatching agent can match the run to
3033
+ * the work item it launched it for without another lookup. When the run failed,
3034
+ * carries the failing step and a set of options too: the agent is being told
3035
+ * about this out of band, with no user question in flight, so without them it
3036
+ * has to guess whether to recover, relaunch, or report.
3037
+ */
3038
+ function finishedRunSummary(record, jobs = []) {
3039
+ const outcome = record.stage === "completed" ? "completed" : `ended in ${record.stage}`;
3040
+ const failure = failurePosition(record, jobs);
3041
+ const lines = [
3042
+ `Workflow run ${record.runKey} in workspace ${record.workspace} ${outcome}.`,
3043
+ record.workflowId ? `Workflow: ${record.workflowId}` : void 0,
3044
+ failure.job ? `Failed job: ${failure.job}${failure.step ? ` (step: ${failure.step})` : ""}` : void 0,
3045
+ record.errorMessage ? `Error: ${record.errorMessage}` : void 0,
3046
+ failure.reason && failure.reason !== record.errorMessage ? `Step reported: ${failure.reason}` : void 0
3047
+ ];
3048
+ const jobId = record.env?.AGIFLOW_JOB_ID;
3049
+ if (jobId) lines.push(`Agiflow job: ${record.env?.AGIFLOW_JOB_KIND ?? "unknown"} ${jobId}`);
3050
+ const summary = lines.filter((line) => Boolean(line)).join("\n");
3051
+ if (record.stage === "completed") return summary;
3052
+ if (record.outcome === "interrupted") return withOptions(summary, [`workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: pick it up where it stopped.`, "Leave it stopped and move on."]);
3053
+ const diagnosticText = [record.errorMessage, failure.reason].filter(Boolean).join("\n");
3054
+ if (diagnosticText.includes("JOB_ALREADY_CLAIMED")) return withOptions(summary, ["Report contention: another worker owns this job. Do not unlock, release, or retry the claim automatically."]);
3055
+ if (diagnosticText.includes("WORKFLOW_NOT_OWNED") || /release failed/i.test(diagnosticText)) return withOptions(summary, ["Inspect the current workflow ownership and ask the user before any release or unlock. Never force another worker’s lock."]);
3056
+ if (/running workflow not found/i.test(diagnosticText)) return withOptions(summary, [`workflow_run {"action":"status","runKey":${JSON.stringify(record.runKey)}}: re-check current state before retrying.`, "Report the recorded terminal state when no running owner remains."]);
3057
+ return withOptions(summary, [
3058
+ `workflow_run {"action":"tail","runKey":${JSON.stringify(record.runKey)}}: read the run's own output first, when the error above does not explain the failure.`,
3059
+ `workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: resume from the failed job, keeping the work already done. Fits a transient or external cause.`,
3060
+ "Report the failure and stop, when it needs a code change or a decision you cannot make."
3061
+ ]);
3062
+ }
3063
+ //#endregion
2915
3064
  //#region src/schemas/workflowRenderGrid.ts
2916
3065
  const renderGridStyleSchema = zod.z.object({
2917
3066
  id: zod.z.number().int().nonnegative(),
@@ -3937,7 +4086,7 @@ function createWorkflowApi(runtime, launch) {
3937
4086
  app = createWorkflowHubApi({
3938
4087
  runtime,
3939
4088
  registry: new require_RecoverWorkflowService.WorkflowRegistryService(runtime, homeDir),
3940
- recordFilter: (record) => context.sessionId === void 0 || record.env?.PO_SESSION_ID === context.sessionId || record.env?.["PO_WORKFLOW_LAUNCHER_SESSION_ID"] === context.sessionId,
4089
+ recordFilter: (record) => context.sessionId === void 0 || isSessionRun(record, context.sessionId) || isLaunchedRun(record, context.sessionId),
3941
4090
  ...steer === void 0 ? {} : { steer: (sessionId, message) => steer(sessionId, message) },
3942
4091
  ...launch === void 0 ? {} : { launch }
3943
4092
  });
@@ -4815,154 +4964,6 @@ function registerRunProvider(ctx) {
4815
4964
  };
4816
4965
  }
4817
4966
  //#endregion
4818
- //#region src/services/piToolBridge/index.ts
4819
- /**
4820
- * Environment variable stamped onto every run launched from a Pi session.
4821
- *
4822
- * `RunWorkflowService` forwards `env` to the inner CLI invocation across the
4823
- * `launch-command` delegation boundary and persists it on the run record, so
4824
- * this survives backgrounding and identifies the launching session afterwards.
4825
- */
4826
- const PO_SESSION_ENV = "PO_SESSION_ID";
4827
- /** Operator override for the per-session launch ceiling. */
4828
- const MAX_CONCURRENT_ENV = "WORKFLOW_MCP_MAX_CONCURRENT";
4829
- const DEFAULT_MAX_CONCURRENT = 5;
4830
- /** Resolve the per-session launch ceiling from the environment. */
4831
- function resolveMaxConcurrent(env) {
4832
- const parsed = Number(env[MAX_CONCURRENT_ENV]);
4833
- return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_MAX_CONCURRENT;
4834
- }
4835
- /**
4836
- * True when this session launched the run.
4837
- *
4838
- * Fails closed on a missing session id: an unstamped record (a CLI launch) and
4839
- * a call arriving before the session id is known both match nothing, so no run
4840
- * ever leaks across sessions through an undefined-equals-undefined comparison.
4841
- */
4842
- function isSessionRun(record, sessionId) {
4843
- return sessionId !== void 0 && record.env?.["PO_SESSION_ID"] === sessionId;
4844
- }
4845
- /** True when this session handed the run to a workflow session of its own: it is told, but does not own it. */
4846
- function isLaunchedRun(record, sessionId) {
4847
- return sessionId !== void 0 && record.env?.["PO_WORKFLOW_LAUNCHER_SESSION_ID"] === sessionId && !isSessionRun(record, sessionId);
4848
- }
4849
- /** The line a launcher's finish notice adds: where the run lived, and where to take it up. */
4850
- function launchedRunHint(record) {
4851
- return `It ran in workflow session ${record.env?.["PO_SESSION_ID"] ?? "unknown"}; troubleshoot and recover it there.`;
4852
- }
4853
- /**
4854
- * True when this session owns a run another session handed to it: it is the
4855
- * run's workflow session, so a finished run is posted without starting a turn.
4856
- */
4857
- function isOwnedDelegatedRun(record, sessionId) {
4858
- const launcher = record.env?.[WORKFLOW_LAUNCHER_SESSION_ENV];
4859
- return isSessionRun(record, sessionId) && typeof launcher === "string" && launcher !== "" && launcher !== sessionId;
4860
- }
4861
- /**
4862
- * What the caller is told when the registry, not the launcher, acknowledged a
4863
- * launch.
4864
- *
4865
- * Leads with the run key because it is the handle every other workflow action
4866
- * takes, and it is no longer available any other way: the launcher's own output
4867
- * is not waited for, so nothing else in this result carries it.
4868
- */
4869
- function launchedRunSummary(record) {
4870
- return [
4871
- `Started ${record.displayName} in workspace ${record.workspace}.`,
4872
- `Run key: ${record.runKey}`,
4873
- "The run is registered and going. This call returned at that point rather than waiting for the launcher process to exit."
4874
- ].join("\n");
4875
- }
4876
- /**
4877
- * What the caller is told when the launch was handed off but no run has
4878
- * registered inside the acknowledgement budget.
4879
- *
4880
- * Deliberately not an error: the launcher is slow rather than broken often
4881
- * enough that failing here would strand runs that go on to start normally.
4882
- * There is no run key to offer yet, so it points at the surfaces that do not
4883
- * need one.
4884
- */
4885
- function launchHandoffSummary(workflowPath) {
4886
- return [
4887
- `Launch handed off for ${workflowPath}, but no run has registered yet.`,
4888
- "Nothing was cancelled: the launcher is still starting, and a run that registers later still reports back here when it finishes.",
4889
- "Do not launch it again. Ask the user to check the active workflow list if they need to see it sooner."
4890
- ].join("\n");
4891
- }
4892
- new RegExp([
4893
- "\\u001B\\[[0-9;?]*[ -/]*[@-~]",
4894
- "\\u001B\\][^\\u0007\\u001B]*(?:\\u0007|\\u001B\\\\)",
4895
- "\\u001B[@-Z\\\\-_]"
4896
- ].join("|"), "g");
4897
- /** Flatten MCP tool content to text for Pi. */
4898
- function toolResultText(result) {
4899
- return result.content.map((part) => part.type === "text" ? part.text : "").filter(Boolean).join("\n").trim();
4900
- }
4901
- /**
4902
- * Attach a short menu of next steps to a problem.
4903
- *
4904
- * An error with no route forward is where an agent invents one: retrying a
4905
- * capacity limit, relaunching a run that should be recovered, or silently
4906
- * giving up on work the user is waiting for. Naming two or three real options
4907
- * turns an error into a decision, and the decision belongs to the user.
4908
- */
4909
- function withOptions(problem, options) {
4910
- if (options.length === 0) return problem;
4911
- return [
4912
- problem,
4913
- "",
4914
- "Options, put these to the user rather than picking one yourself:",
4915
- ...options.map(bullet)
4916
- ].join("\n");
4917
- }
4918
- function bullet(option) {
4919
- return `- ${option}`;
4920
- }
4921
- /** The job and step a run died on, preferring the progress log to the record. */
4922
- function failurePosition(record, jobs) {
4923
- const failedJob = jobs.find((job) => job.status === "failed");
4924
- const failedStep = failedJob?.steps.find((step) => step.status === "failed");
4925
- return {
4926
- job: failedJob?.name ?? record.failedJob,
4927
- step: failedStep?.name,
4928
- reason: failedStep?.reason ?? failedJob?.reason
4929
- };
4930
- }
4931
- /**
4932
- * Describe a finished run for the agent that launched it.
4933
- *
4934
- * Carries the job identifiers back so a dispatching agent can match the run to
4935
- * the work item it launched it for without another lookup. When the run failed,
4936
- * carries the failing step and a set of options too: the agent is being told
4937
- * about this out of band, with no user question in flight, so without them it
4938
- * has to guess whether to recover, relaunch, or report.
4939
- */
4940
- function finishedRunSummary(record, jobs = []) {
4941
- const outcome = record.stage === "completed" ? "completed" : `ended in ${record.stage}`;
4942
- const failure = failurePosition(record, jobs);
4943
- const lines = [
4944
- `Workflow run ${record.runKey} in workspace ${record.workspace} ${outcome}.`,
4945
- record.workflowId ? `Workflow: ${record.workflowId}` : void 0,
4946
- failure.job ? `Failed job: ${failure.job}${failure.step ? ` (step: ${failure.step})` : ""}` : void 0,
4947
- record.errorMessage ? `Error: ${record.errorMessage}` : void 0,
4948
- failure.reason && failure.reason !== record.errorMessage ? `Step reported: ${failure.reason}` : void 0
4949
- ];
4950
- const jobId = record.env?.AGIFLOW_JOB_ID;
4951
- if (jobId) lines.push(`Agiflow job: ${record.env?.AGIFLOW_JOB_KIND ?? "unknown"} ${jobId}`);
4952
- const summary = lines.filter((line) => Boolean(line)).join("\n");
4953
- if (record.stage === "completed") return summary;
4954
- if (record.outcome === "interrupted") return withOptions(summary, [`workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: pick it up where it stopped.`, "Leave it stopped and move on."]);
4955
- const diagnosticText = [record.errorMessage, failure.reason].filter(Boolean).join("\n");
4956
- if (diagnosticText.includes("JOB_ALREADY_CLAIMED")) return withOptions(summary, ["Report contention: another worker owns this job. Do not unlock, release, or retry the claim automatically."]);
4957
- if (diagnosticText.includes("WORKFLOW_NOT_OWNED") || /release failed/i.test(diagnosticText)) return withOptions(summary, ["Inspect the current workflow ownership and ask the user before any release or unlock. Never force another worker’s lock."]);
4958
- if (/running workflow not found/i.test(diagnosticText)) return withOptions(summary, [`workflow_run {"action":"status","runKey":${JSON.stringify(record.runKey)}}: re-check current state before retrying.`, "Report the recorded terminal state when no running owner remains."]);
4959
- return withOptions(summary, [
4960
- `workflow_run {"action":"tail","runKey":${JSON.stringify(record.runKey)}}: read the run's own output first, when the error above does not explain the failure.`,
4961
- `workflow_run {"action":"recover","runKey":${JSON.stringify(record.runKey)}}: resume from the failed job, keeping the work already done. Fits a transient or external cause.`,
4962
- "Report the failure and stop, when it needs a code change or a decision you cannot make."
4963
- ]);
4964
- }
4965
- //#endregion
4966
4967
  //#region src/services/workflowExecution/index.ts
4967
4968
  const AGIFLOW_JOB_ID_ENV = "AGIFLOW_JOB_ID";
4968
4969
  const AGIFLOW_JOB_KIND_ENV = "AGIFLOW_JOB_KIND";