gm-skill 2.0.1157 → 2.0.1159

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/README.md CHANGED
@@ -35,7 +35,7 @@ An earlier generation fanned out fifteen per-platform downstream repos (gm-cc, g
35
35
 
36
36
  ## Version
37
37
 
38
- `2.0.1157` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
38
+ `2.0.1159` — auto-bumped from the canonical `gm` repo. Every push to `AnEntrypoint/gm` (or any cascading sibling crate) republishes this package.
39
39
 
40
40
  ## Source of truth
41
41
 
@@ -1 +1 @@
1
- 0.1.417
1
+ 0.1.418
package/bin/plugkit.wasm CHANGED
Binary file
@@ -1 +1 @@
1
- 7878f3d4cf0bbe92dc6eb38b9d033d2f7006d46ce16f7acafbcdffd377785aa9 plugkit.wasm
1
+ ffb68924055bffd275db6a174fb5d286c1fcaf5a135f968345bf221b9976544f plugkit.wasm
@@ -435,6 +435,192 @@ function kvFilePath(ns, key) {
435
435
  return path.join(dir, safeKey + '.json');
436
436
  }
437
437
 
438
+ const __tasks = new Map();
439
+
440
+ function tasksDir(cwd) {
441
+ const d = path.join(cwd || process.cwd(), '.gm', 'exec-spool', 'tasks');
442
+ try { fs.mkdirSync(d, { recursive: true }); } catch (_) {}
443
+ return d;
444
+ }
445
+
446
+ function taskMetaPath(cwd, id) { return path.join(tasksDir(cwd), `${id}.json`); }
447
+ function taskOutPath(cwd, id, which) { return path.join(tasksDir(cwd), `${id}.${which}.log`); }
448
+
449
+ function writeTaskMeta(cwd, id, meta) {
450
+ try { fs.writeFileSync(taskMetaPath(cwd, id), JSON.stringify(meta, null, 2)); } catch (_) {}
451
+ }
452
+
453
+ function nextTaskId(cwd) {
454
+ const counterPath = path.join(tasksDir(cwd), '.counter');
455
+ let n = 0;
456
+ try { n = parseInt(fs.readFileSync(counterPath, 'utf-8'), 10) || 0; } catch (_) {}
457
+ n += 1;
458
+ try { fs.writeFileSync(counterPath, String(n)); } catch (_) {}
459
+ return `t${n}`;
460
+ }
461
+
462
+ function langToCmd(lang, code) {
463
+ if (lang === 'nodejs' || lang === 'js' || lang === 'javascript' || lang === 'node') return { cmd: process.execPath, args: ['-e', code], stdinCode: null };
464
+ if (lang === 'python' || lang === 'py') return { cmd: 'python', args: ['-c', code], stdinCode: null };
465
+ if (lang === 'bash' || lang === 'sh' || lang === 'shell' || lang === 'zsh') return { cmd: 'bash', args: ['-c', code], stdinCode: null };
466
+ if (lang === 'powershell' || lang === 'ps1') return { cmd: 'powershell', args: ['-NoProfile', '-NonInteractive', '-Command', code], stdinCode: null };
467
+ if (lang === 'deno') return { cmd: 'deno', args: ['eval', code], stdinCode: null };
468
+ return null;
469
+ }
470
+
471
+ function spawnTask({ cwd, lang, code, timeoutMs }) {
472
+ const id = nextTaskId(cwd);
473
+ const built = langToCmd(lang, code);
474
+ if (!built) return { ok: false, error: `unsupported lang: ${lang}` };
475
+ const outLog = taskOutPath(cwd, id, 'stdout');
476
+ const errLog = taskOutPath(cwd, id, 'stderr');
477
+ let outFd = null, errFd = null;
478
+ try { outFd = fs.openSync(outLog, 'a'); } catch (_) {}
479
+ try { errFd = fs.openSync(errLog, 'a'); } catch (_) {}
480
+ const startedMs = Date.now();
481
+ const isPosix = process.platform !== 'win32';
482
+ const child = spawn(built.cmd, built.args, {
483
+ cwd: cwd || process.cwd(),
484
+ detached: isPosix,
485
+ stdio: ['ignore', outFd || 'ignore', errFd || 'ignore'],
486
+ windowsHide: true,
487
+ env: process.env,
488
+ });
489
+ try { if (outFd !== null) fs.closeSync(outFd); } catch (_) {}
490
+ try { if (errFd !== null) fs.closeSync(errFd); } catch (_) {}
491
+ const meta = {
492
+ id,
493
+ pid: child.pid,
494
+ pgid: isPosix ? child.pid : null,
495
+ lang,
496
+ cmd: built.cmd,
497
+ cwd: cwd || process.cwd(),
498
+ started_ms: startedMs,
499
+ timeout_ms: timeoutMs,
500
+ deadline_ms: startedMs + timeoutMs,
501
+ status: 'running',
502
+ exit_code: null,
503
+ stdout_log: outLog,
504
+ stderr_log: errLog,
505
+ };
506
+ __tasks.set(id, { child, meta });
507
+ writeTaskMeta(cwd, id, meta);
508
+ child.on('exit', (code, signal) => {
509
+ meta.status = signal ? 'killed' : (code === 0 ? 'completed' : 'failed');
510
+ meta.exit_code = code;
511
+ meta.signal = signal;
512
+ meta.ended_ms = Date.now();
513
+ writeTaskMeta(meta.cwd, id, meta);
514
+ });
515
+ child.on('error', (err) => {
516
+ meta.status = 'error';
517
+ meta.error = err.message;
518
+ meta.ended_ms = Date.now();
519
+ writeTaskMeta(meta.cwd, id, meta);
520
+ });
521
+ logEvent('plugkit', 'task.spawn', { task_id: id, pid: child.pid, lang, timeout_ms: timeoutMs });
522
+ return { ok: true, task_id: id, pid: child.pid, started_ms: startedMs };
523
+ }
524
+
525
+ function stopTaskById(id) {
526
+ const entry = __tasks.get(id);
527
+ if (!entry) {
528
+ return { ok: false, error: 'unknown task_id', task_id: id };
529
+ }
530
+ const { child, meta } = entry;
531
+ if (meta.status !== 'running') return { ok: true, already: meta.status, task_id: id };
532
+ const pid = meta.pid;
533
+ const isPosix = process.platform !== 'win32';
534
+ try {
535
+ if (isPosix && meta.pgid) {
536
+ try { process.kill(-meta.pgid, 'SIGTERM'); } catch (_) {}
537
+ } else {
538
+ try { child.kill('SIGTERM'); } catch (_) {}
539
+ }
540
+ } catch (_) {}
541
+ const graceTimer = setTimeout(() => {
542
+ if (meta.status !== 'running') return;
543
+ if (isPosix && meta.pgid) {
544
+ try { process.kill(-meta.pgid, 'SIGKILL'); } catch (_) {}
545
+ } else if (process.platform === 'win32') {
546
+ try { spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], { stdio: 'ignore', timeout: 3000 }); } catch (_) {}
547
+ } else {
548
+ try { child.kill('SIGKILL'); } catch (_) {}
549
+ }
550
+ }, 2000);
551
+ graceTimer.unref && graceTimer.unref();
552
+ logEvent('plugkit', 'task.stop', { task_id: id, pid });
553
+ return { ok: true, task_id: id, pid };
554
+ }
555
+
556
+ function tailFile(filePath, maxBytes) {
557
+ try {
558
+ const stat = fs.statSync(filePath);
559
+ if (stat.size <= maxBytes) return fs.readFileSync(filePath, 'utf-8');
560
+ const fd = fs.openSync(filePath, 'r');
561
+ try {
562
+ const buf = Buffer.alloc(maxBytes);
563
+ fs.readSync(fd, buf, 0, maxBytes, stat.size - maxBytes);
564
+ return buf.toString('utf-8');
565
+ } finally { try { fs.closeSync(fd); } catch (_) {} }
566
+ } catch (_) { return ''; }
567
+ }
568
+
569
+ function listTasks(cwd) {
570
+ const d = tasksDir(cwd);
571
+ const out = [];
572
+ try {
573
+ for (const entry of fs.readdirSync(d)) {
574
+ if (!entry.endsWith('.json') || entry.startsWith('.')) continue;
575
+ try {
576
+ const meta = JSON.parse(fs.readFileSync(path.join(d, entry), 'utf-8'));
577
+ out.push(meta);
578
+ } catch (_) {}
579
+ }
580
+ } catch (_) {}
581
+ return out;
582
+ }
583
+
584
+ function reapTimedOutTasks() {
585
+ const now = Date.now();
586
+ for (const [id, entry] of __tasks) {
587
+ const m = entry.meta;
588
+ if (m.status === 'running' && m.deadline_ms && now > m.deadline_ms) {
589
+ logEvent('plugkit', 'task.timeout', { task_id: id, pid: m.pid, deadline_ms: m.deadline_ms, now_ms: now });
590
+ stopTaskById(id);
591
+ }
592
+ }
593
+ }
594
+
595
+ function killAllTasks(reason) {
596
+ let killed = 0;
597
+ for (const [id, entry] of __tasks) {
598
+ if (entry.meta.status === 'running') {
599
+ stopTaskById(id);
600
+ killed += 1;
601
+ }
602
+ }
603
+ if (killed > 0) logEvent('plugkit', 'task.killAll', { reason, count: killed });
604
+ return killed;
605
+ }
606
+
607
+ function hostTaskProc(action, params) {
608
+ switch (action) {
609
+ case 'spawn': return spawnTask(params);
610
+ case 'stop': return stopTaskById(params.id || params.task_id);
611
+ case 'list': return { ok: true, tasks: listTasks(params.cwd) };
612
+ case 'output': return {
613
+ ok: true,
614
+ task_id: params.id || params.task_id,
615
+ stdout: tailFile(taskOutPath(params.cwd, params.id || params.task_id, 'stdout'), params.max_bytes || 65536),
616
+ stderr: tailFile(taskOutPath(params.cwd, params.id || params.task_id, 'stderr'), params.max_bytes || 65536),
617
+ };
618
+ case 'reap': { reapTimedOutTasks(); return { ok: true }; }
619
+ case 'killAll': { const n = killAllTasks(params.reason || 'host_task_proc'); return { ok: true, killed: n }; }
620
+ default: return { ok: false, error: `unknown action: ${action}` };
621
+ }
622
+ }
623
+
438
624
  function makeHostFunctions(instanceRef) {
439
625
  return {
440
626
  host_fs_read: (pathPtr, pathLen) => {
@@ -713,6 +899,19 @@ function makeHostFunctions(instanceRef) {
713
899
  return 0n;
714
900
  }
715
901
  },
902
+
903
+ host_task_proc: (actionPtr, actionLen, paramsPtr, paramsLen) => {
904
+ try {
905
+ const action = readWasmStr(instanceRef.value, actionPtr, actionLen);
906
+ const paramsStr = readWasmStr(instanceRef.value, paramsPtr, paramsLen);
907
+ const params = paramsStr ? JSON.parse(paramsStr) : {};
908
+ if (!params.cwd) params.cwd = process.cwd();
909
+ const result = hostTaskProc(action, params);
910
+ return writeWasmJson(instanceRef.value, result);
911
+ } catch (e) {
912
+ return writeWasmJson(instanceRef.value, { ok: false, error: e.message });
913
+ }
914
+ },
716
915
  };
717
916
  }
718
917
 
@@ -797,6 +996,8 @@ async function runSpoolWatcher(instance, spoolDir) {
797
996
  console.log(`[plugkit-wasm] teardown reason=${reason}`);
798
997
  } catch (_) {}
799
998
 
999
+ try { killAllTasks(`teardown:${reason}`); } catch (_) {}
1000
+
800
1001
  try {
801
1002
  if (fs.existsSync(ACPTOAPI_STATUS_PATH)) {
802
1003
  const status = JSON.parse(fs.readFileSync(ACPTOAPI_STATUS_PATH, 'utf-8'));
@@ -830,6 +1031,10 @@ async function runSpoolWatcher(instance, spoolDir) {
830
1031
  process.exit(0);
831
1032
  }
832
1033
 
1034
+ setInterval(() => {
1035
+ try { reapTimedOutTasks(); } catch (_) {}
1036
+ }, 5000);
1037
+
833
1038
  setInterval(() => {
834
1039
  try {
835
1040
  const idleMs = Date.now() - lastActivityMs;
@@ -842,6 +1047,13 @@ async function runSpoolWatcher(instance, spoolDir) {
842
1047
  }
843
1048
  if (browserAlive) { markActivity('browser-port-alive'); return; }
844
1049
  } catch (_) {}
1050
+ try {
1051
+ let anyRunning = false;
1052
+ for (const entry of __tasks.values()) {
1053
+ if (entry.meta.status === 'running') { anyRunning = true; break; }
1054
+ }
1055
+ if (anyRunning) { markActivity('task-running'); return; }
1056
+ } catch (_) {}
845
1057
  teardownAll('idle');
846
1058
  } catch (e) {
847
1059
  console.error(`[idle-check] error: ${e.message}`);
package/gm.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm",
3
- "version": "2.0.1157",
3
+ "version": "2.0.1159",
4
4
  "description": "Spool-dispatch orchestration engine with unified state machine, skills, and automated git enforcement",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -17,5 +17,5 @@
17
17
  "publishConfig": {
18
18
  "access": "public"
19
19
  },
20
- "plugkitVersion": "0.1.417"
20
+ "plugkitVersion": "0.1.418"
21
21
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gm-skill",
3
- "version": "2.0.1157",
3
+ "version": "2.0.1159",
4
4
  "description": "Canonical universal harness — AI-native software engineering via skill-driven orchestration; bootstraps plugkit for task execution and session isolation. Install in any AI coding agent host.",
5
5
  "author": "AnEntrypoint",
6
6
  "license": "MIT",
@@ -39,7 +39,7 @@
39
39
  "gm.json"
40
40
  ],
41
41
  "dependencies": {
42
- "gm-plugkit": "^2.0.1157"
42
+ "gm-plugkit": "^2.0.1159"
43
43
  },
44
44
  "engines": {
45
45
  "node": ">=16.0.0"