taskforce-loop-engineering 0.9.0 → 0.9.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.1 - 2026-08-11
4
+
5
+ - Recognize explicit `用 loop engineering` requests that ask to align, complete, enhance, develop, or build a system as executable Loop tasks instead of direct chat.
6
+ - Make the generated OpenClaw conversation wrapper fail closed when channel, target, account, or message-id routing metadata is missing, preventing unscoped human-gate and terminal tasks.
7
+ - Document the mandatory wrapper and notification-delivery postcondition, and add regression coverage for the original Growth OS phrasing and missing-source failure.
8
+
3
9
  ## 0.9.0 - 2026-08-08
4
10
 
5
11
  - Add a native Hermes Agent conversation integration with a plan-first installer, `hermes -z` one-shot worker dispatcher, `hermes send` notifier, managed scheduler, and source-bound delivery routing.
package/lib/core.mjs CHANGED
@@ -1520,7 +1520,7 @@ export function classifyLoopMessage(message) {
1520
1520
  const mentionsLoop = /(loop engineering|loop-engineering|task-runner|队列|queue|\bloop\b)/i.test(text);
1521
1521
  const statusIntent = mentionsLoop
1522
1522
  && /(查|看|检查|审计|状态|情况|进度|怎么样|为什么|失败|报错|健康|health|status|progress|audit|summar)/i.test(text);
1523
- const executeIntent = /(走\s*loop|继续(?:当前|这个)?\s*loop|给(?:当前|这个)?\s*loop\s*(?:补充|增加|加)|用\s*loop.*(?:解决|执行|完成|处理|修复|绕过|避开|跳过|bypass|evade)|丢进.*loop|入队|enqueue|run[- ]?queue|立刻执行|立即执行)/i.test(text);
1523
+ const executeIntent = /(走\s*loop|继续(?:当前|这个)?\s*loop|给(?:当前|这个)?\s*loop\s*(?:补充|增加|加)|用\s*loop.*(?:解决|执行|完成|处理|修复|对齐|补齐|增强|开发|构建|绕过|避开|跳过|bypass|evade)|丢进.*loop|入队|enqueue|run[- ]?queue|立刻执行|立即执行)/i.test(text);
1524
1524
  const intent = executeIntent ? 'execute' : statusIntent ? 'status' : 'direct';
1525
1525
  return {
1526
1526
  intent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskforce-loop-engineering",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "private": false,
5
5
  "description": "OpenClaw-native loop engineering CLI, templates, and skill for verifiable agent work loops.",
6
6
  "type": "module",
@@ -62,7 +62,13 @@ if (queue.retry?.runtimeRecoveryMaxAttempts !== 2 || queue.retry?.sessionMaxTick
62
62
  const instructions = await readFile(path.join(root, 'AGENTS.md'), 'utf8');
63
63
  if (!instructions.includes('走 loop') || !instructions.includes('immediately execute')) throw new Error('conversation instructions missing');
64
64
  const wrapper = await readFile(path.join(root, 'scripts/loops/openclaw-loop.mjs'), 'utf8');
65
- if (!wrapper.includes('--supersede-active') || !wrapper.includes('--amend-active') || !wrapper.includes('--progress-notify-command') || !wrapper.includes('runWhenUnlocked') || wrapper.includes("run-queue-drain', '--config'") || wrapper.includes("spawn('loop-engineering'") || !wrapper.includes('queue-human-input-notify') || !wrapper.includes('queue-terminal-notify') || !wrapper.includes('queue-scheduler-tick') || !wrapper.includes('只入队')) throw new Error('supersede/amend routing, absolute CLI, scheduler, live progress, async notification, or queue-only routing missing');
65
+ if (!wrapper.includes('--supersede-active') || !wrapper.includes('--amend-active') || !wrapper.includes('--progress-notify-command') || !wrapper.includes('runWhenUnlocked') || wrapper.includes("run-queue-drain', '--config'") || wrapper.includes("spawn('loop-engineering'") || !wrapper.includes('queue-human-input-notify') || !wrapper.includes('queue-terminal-notify') || !wrapper.includes('queue-scheduler-tick') || !wrapper.includes('只入队') || !wrapper.includes('requiredSource') || !wrapper.includes('--source-message-id')) throw new Error('supersede/amend routing, source fail-closed policy, absolute CLI, scheduler, live progress, async notification, or queue-only routing missing');
66
+ const missingSourceRoute = await new Promise((resolve) => {
67
+ const child = spawn(process.execPath, [path.join(root, 'scripts/loops/openclaw-loop.mjs'), 'route', '--message', '用 loop engineering 对齐系统'], { cwd: root, stdio: ['ignore', 'pipe', 'pipe'] });
68
+ let stderr = ''; child.stderr.on('data', (chunk) => { stderr += chunk; });
69
+ child.on('close', (code) => resolve({ code, stderr }));
70
+ });
71
+ if (missingSourceRoute.code !== 2 || !missingSourceRoute.stderr.includes('requires conversation metadata')) throw new Error('installed wrapper did not fail closed without source routing');
66
72
  const serviceFile = path.join(process.env.XDG_CONFIG_HOME, 'systemd/user/openclaw-loop-test-tasks-scheduler.service');
67
73
  const timerFile = path.join(process.env.XDG_CONFIG_HOME, 'systemd/user/openclaw-loop-test-tasks-scheduler.timer');
68
74
  const service = await readFile(serviceFile, 'utf8');
@@ -144,6 +144,17 @@ async function runWhenUnlocked(args, waitMs = 300000) {
144
144
  if (command === 'route') {
145
145
  const messageIndex = rest.indexOf('--message');
146
146
  const message = messageIndex >= 0 ? String(rest[messageIndex + 1] || '') : '';
147
+ const optionValue = (name) => {
148
+ const index = rest.indexOf(name);
149
+ return index >= 0 ? String(rest[index + 1] || '').trim() : '';
150
+ };
151
+ const requiredSource = ['--source-channel', '--source-target', '--source-account', '--source-message-id'];
152
+ const missingSource = requiredSource.filter((name) => !optionValue(name));
153
+ if (missingSource.length) {
154
+ console.error(\`loop route requires conversation metadata: \${missingSource.join(', ')}\`);
155
+ process.exitCode = 2;
156
+ process.exit();
157
+ }
147
158
  const amendment = /(?:继续(?:当前|这个)?\\s*loop|给(?:当前|这个)?\\s*loop\\s*(?:补充|增加|加)|补充当前\\s*loop)/i.test(message);
148
159
  const routeMode = amendment ? '--amend-active' : '--supersede-active';
149
160
  const routeCode = await run(['route-message', '--queue', ${JSON.stringify(queue)}, '--route', '--confirm-execute', routeMode, ...rest]);
@@ -203,7 +214,10 @@ function instructionsBlock({ queue }) {
203
214
  ## Loop Engineering conversation routing
204
215
 
205
216
  - Route only explicit loop requests. \`走 loop\` means enqueue and immediately execute one tick; only \`只入队\` or \`只排队\` suppresses execution.
217
+ - Treat explicit phrases such as \`用 loop engineering\`, \`丢进 Ironman loop\`, \`loop Ironman\`, and \`task-runner\` as Loop requests too.
206
218
  - Run \`node scripts/loops/openclaw-loop.mjs route --message "<full user message>"\` from this workspace and preserve source metadata when available.
219
+ - For conversation-originated work, the standard wrapper is mandatory. Missing source metadata must fail closed; never fall back to manual enqueue or direct run-queue.
220
+ - A human-gated or terminal state is not delivered until its notification command succeeds and writes a notification record.
207
221
  - An already loop-managed task must be executed directly and never routed again.
208
222
  - Status questions are read-only. High-risk external, destructive, production, credential, or memory migration actions remain separately gated.
209
223
  - Queue: \`${queue}\`.
@@ -48,6 +48,12 @@ assert.deepEqual(classifyLoopMessage('用 loop engineering 绕过某个检查'),
48
48
  enqueue: true,
49
49
  readOnly: false
50
50
  });
51
+ assert.deepEqual(classifyLoopMessage('用 loop engineering 把现有的 growth os 与文件对齐,把需要增强的功能补齐'), {
52
+ intent: 'execute',
53
+ risk: 'model_assessed',
54
+ enqueue: true,
55
+ readOnly: false
56
+ });
51
57
 
52
58
  const routed = await routeLoopMessage(root, {
53
59
  route: true,