poe-code 3.0.341 → 3.0.343

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "poe-code",
3
- "version": "3.0.341",
3
+ "version": "3.0.343",
4
4
  "description": "CLI tool to configure Poe API for developer workflows.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,11 +46,12 @@ export function resolveConfig(raw, cwd) {
46
46
  intervalMs: readPositiveInteger(getOwnEntry(polling, "interval_ms"), 30_000, "polling.interval_ms")
47
47
  },
48
48
  workspace: {
49
- root: resolvePathValue(readString(getOwnEntry(workspace, "root")) ?? path.join(os.tmpdir(), "poe-code-maestro"), cwd)
49
+ root: resolvePathValue(readString(getOwnEntry(workspace, "root"), "workspace.root") ??
50
+ path.join(os.tmpdir(), "poe-code-maestro"), cwd)
50
51
  },
51
52
  agent: {
52
- service: readString(getOwnEntry(agent, "service")) ?? "codex",
53
- list: readString(resolveStringValue(getOwnEntry(agent, "list"))),
53
+ service: readString(getOwnEntry(agent, "service"), "agent.service") ?? "codex",
54
+ list: readString(resolveStringValue(getOwnEntry(agent, "list")), "agent.list"),
54
55
  maxConcurrentAgents: readPositiveInteger(getOwnEntry(agent, "max_concurrent_agents"), 1, "agent.max_concurrent_agents"),
55
56
  maxRetryBackoffMs: readNonNegativeInteger(getOwnEntry(agent, "max_retry_backoff_ms"), 300_000, "agent.max_retry_backoff_ms")
56
57
  }
@@ -154,25 +155,34 @@ function expandHome(value) {
154
155
  }
155
156
  return value;
156
157
  }
157
- function readString(value) {
158
- if (typeof value !== "string") {
158
+ function readString(value, field) {
159
+ if (value === undefined) {
159
160
  return undefined;
160
161
  }
162
+ if (typeof value !== "string") {
163
+ throw new Error(`Expected "${field}" to be a string.`);
164
+ }
161
165
  const trimmed = value.trim();
162
166
  return trimmed.length > 0 ? value : undefined;
163
167
  }
164
- function readNumber(value, fallback) {
165
- return typeof value === "number" && Number.isFinite(value) ? value : fallback;
168
+ function readNumber(value, fallback, field) {
169
+ if (value === undefined) {
170
+ return fallback;
171
+ }
172
+ if (typeof value !== "number" || !Number.isFinite(value)) {
173
+ throw new Error(`Expected "${field}" to be a number.`);
174
+ }
175
+ return value;
166
176
  }
167
177
  function readPositiveInteger(value, fallback, field) {
168
- const numberValue = readNumber(value, fallback);
178
+ const numberValue = readNumber(value, fallback, field);
169
179
  if (!Number.isInteger(numberValue) || numberValue <= 0) {
170
180
  throw new Error(`Expected "${field}" to be a positive integer.`);
171
181
  }
172
182
  return numberValue;
173
183
  }
174
184
  function readNonNegativeInteger(value, fallback, field) {
175
- const numberValue = readNumber(value, fallback);
185
+ const numberValue = readNumber(value, fallback, field);
176
186
  if (!Number.isInteger(numberValue) || numberValue < 0) {
177
187
  throw new Error(`Expected "${field}" to be a non-negative integer.`);
178
188
  }
@@ -8,10 +8,14 @@ export function validateStateDefinitions(value) {
8
8
  throw new Error("Workflow config requires at least one state.");
9
9
  }
10
10
  for (const [name, definition] of entries) {
11
+ const stateName = String(name);
12
+ if (stateName.trim().length === 0) {
13
+ throw new Error("State names must not be empty.");
14
+ }
11
15
  if (!isRecord(definition)) {
12
- throw new Error(`State "${String(name)}" must be an object.`);
16
+ throw new Error(`State "${stateName}" must be an object.`);
13
17
  }
14
- validateStateDefinition(String(name), definition);
18
+ validateStateDefinition(stateName, definition);
15
19
  }
16
20
  }
17
21
  export async function validateDispatch(cfg, taskList) {
@@ -86,6 +90,9 @@ function validateStateDefinition(name, definition) {
86
90
  if (prompt !== undefined && typeof prompt !== "string") {
87
91
  throw new Error(`State "${name}" prompt must be a string.`);
88
92
  }
93
+ if (typeof prompt === "string" && prompt.trim().length === 0) {
94
+ throw new Error(`State "${name}" prompt must not be empty.`);
95
+ }
89
96
  if (terminal !== undefined && typeof terminal !== "boolean") {
90
97
  throw new Error(`State "${name}" terminal must be a boolean.`);
91
98
  }
@@ -7,5 +7,20 @@ export function resolveWorkflowPath(name, cwd) {
7
7
  return path.resolve(cwd, filename);
8
8
  }
9
9
  function isValidWorkflowName(name) {
10
- return name.length > 0 && !name.includes("/") && !name.includes("\\");
10
+ if (name.length === 0 || name.trim() !== name || name === "." || name === "..") {
11
+ return false;
12
+ }
13
+ for (const character of name) {
14
+ if (!isWorkflowNameCharacter(character)) {
15
+ return false;
16
+ }
17
+ }
18
+ return true;
19
+ }
20
+ function isWorkflowNameCharacter(character) {
21
+ const code = character.charCodeAt(0);
22
+ const isUppercase = code >= 65 && code <= 90;
23
+ const isLowercase = code >= 97 && code <= 122;
24
+ const isDigit = code >= 48 && code <= 57;
25
+ return isUppercase || isLowercase || isDigit || character === "_" || character === "-";
11
26
  }
@@ -5,11 +5,16 @@ export function buildOpenSourceAction(options) {
5
5
  id: "open-source",
6
6
  key: "o",
7
7
  label: "Open in $EDITOR",
8
- predicate: (ctx) => getTask(options.taskByRowId(), ctx.row.id).sourcePath != null,
8
+ predicate: (ctx) => getSourcePath(getTask(options.taskByRowId(), ctx.row.id)) !== null,
9
9
  handler: async (ctx) => {
10
10
  const task = getTask(options.taskByRowId(), ctx.row.id);
11
+ const sourcePath = getSourcePath(task);
12
+ if (sourcePath === null) {
13
+ ctx.toast("No source file available.", "info");
14
+ return;
15
+ }
11
16
  await ctx.suspendAnd(async () => {
12
- editFile(task.sourcePath, { env: options.variables });
17
+ editFile(sourcePath, { env: options.variables });
13
18
  });
14
19
  await ctx.refresh();
15
20
  ctx.toast(`Edited ${task.qualifiedId}`, "info");
@@ -41,14 +46,25 @@ function getIssueUrl(task) {
41
46
  if (typeof url !== "string") {
42
47
  return null;
43
48
  }
49
+ const trimmedUrl = url.trim();
50
+ if (trimmedUrl.length === 0) {
51
+ return null;
52
+ }
44
53
  try {
45
- const parsedUrl = new URL(url);
46
- return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" ? url : null;
54
+ const parsedUrl = new URL(trimmedUrl);
55
+ return parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" ? trimmedUrl : null;
47
56
  }
48
57
  catch {
49
58
  return null;
50
59
  }
51
60
  }
61
+ function getSourcePath(task) {
62
+ const sourcePath = task.sourcePath;
63
+ if (typeof sourcePath !== "string" || sourcePath.trim().length === 0) {
64
+ return null;
65
+ }
66
+ return sourcePath;
67
+ }
52
68
  function getTask(taskByRowId, rowId) {
53
69
  const task = taskByRowId.get(rowId);
54
70
  if (task === undefined) {
@@ -85,6 +85,7 @@ async function renderTaskDetailMarkdown(task, taskList) {
85
85
  const eventsMarkdown = await renderEventsMarkdown(task, taskList);
86
86
  const description = task.description.length > 0 ? task.description : "_No description._";
87
87
  const metadata = stringify(task.metadata).trimEnd();
88
+ const metadataFence = buildBacktickFence(metadata);
88
89
  return [
89
90
  `# ${task.name}`,
90
91
  "",
@@ -94,9 +95,9 @@ async function renderTaskDetailMarkdown(task, taskList) {
94
95
  "",
95
96
  "## Metadata",
96
97
  "",
97
- "```yaml",
98
+ `${metadataFence}yaml`,
98
99
  metadata,
99
- "```",
100
+ metadataFence,
100
101
  "",
101
102
  "## Next",
102
103
  "",
@@ -112,9 +113,45 @@ async function renderEventsMarkdown(task, taskList) {
112
113
  return events.map((event) => `- ${event}`).join("\n");
113
114
  }
114
115
  catch (err) {
115
- return `_Could not load events: ${err.message}_`;
116
+ return `_Could not load events: ${formatThrownValue(err)}_`;
116
117
  }
117
118
  }
119
+ function buildBacktickFence(content) {
120
+ return "`".repeat(Math.max(3, longestCharacterRun(content, "`") + 1));
121
+ }
122
+ function longestCharacterRun(content, expected) {
123
+ let longest = 0;
124
+ let current = 0;
125
+ for (const character of content) {
126
+ if (character === expected) {
127
+ current += 1;
128
+ longest = Math.max(longest, current);
129
+ }
130
+ else {
131
+ current = 0;
132
+ }
133
+ }
134
+ return longest;
135
+ }
136
+ function formatThrownValue(value) {
137
+ if (value instanceof Error && value.message.length > 0) {
138
+ return value.message;
139
+ }
140
+ if (typeof value === "string" && value.length > 0) {
141
+ return value;
142
+ }
143
+ try {
144
+ const json = JSON.stringify(value);
145
+ if (json !== undefined && json.length > 0) {
146
+ return json;
147
+ }
148
+ }
149
+ catch {
150
+ // Fall back to String below.
151
+ }
152
+ const text = String(value);
153
+ return text.length > 0 ? text : "unknown error";
154
+ }
118
155
  function toTaskMap(tasks) {
119
156
  const taskByRowId = new Map();
120
157
  for (const task of tasks) {