instar 0.28.96 → 0.28.98
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/dist/core/types.d.ts +30 -4
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/scheduler/AgentMdJobLoader.d.ts +104 -0
- package/dist/scheduler/AgentMdJobLoader.d.ts.map +1 -0
- package/dist/scheduler/AgentMdJobLoader.js +805 -0
- package/dist/scheduler/AgentMdJobLoader.js.map +1 -0
- package/dist/scheduler/JobLoader.d.ts +4 -0
- package/dist/scheduler/JobLoader.d.ts.map +1 -1
- package/dist/scheduler/JobLoader.js +59 -3
- package/dist/scheduler/JobLoader.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +31 -1
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +195 -3
- package/dist/server/routes.js.map +1 -1
- package/dist/tasks/task-flow-types.d.ts +1 -1
- package/package.json +3 -1
- package/scripts/upgrade-guide-validator.mjs +3 -4
- package/src/data/builtin-manifest.json +48 -48
- package/upgrades/0.28.97.md +146 -0
- package/upgrades/0.28.98.md +47 -0
- package/upgrades/side-effects/NEXT.md +68 -61
- package/upgrades/side-effects/jobs-as-agentmd-phase-1a.md +153 -0
- package/upgrades/side-effects/project-scope-phase1-7-skill-surface.md +175 -0
package/dist/server/routes.js
CHANGED
|
@@ -496,11 +496,12 @@ function pickFields(obj, fields) {
|
|
|
496
496
|
}
|
|
497
497
|
return out;
|
|
498
498
|
}
|
|
499
|
-
/** Maps a /projects/:id/next action verb to the suggested skill invocation.
|
|
499
|
+
/** Maps a /projects/:id/next action verb to the suggested skill invocation.
|
|
500
|
+
* Names are the canonical command set from PROJECT-SCOPE-SPEC § Phase 1.7. */
|
|
500
501
|
function skillCommandForAction(action, projectId, roundIndex) {
|
|
501
502
|
switch (action) {
|
|
502
503
|
case 'await-user-approval':
|
|
503
|
-
return `/project
|
|
504
|
+
return `/project ack ${projectId}`;
|
|
504
505
|
case 'ack-required':
|
|
505
506
|
return `/project ack ${projectId}`;
|
|
506
507
|
case 'resolve-conflict':
|
|
@@ -510,7 +511,7 @@ function skillCommandForAction(action, projectId, roundIndex) {
|
|
|
510
511
|
case 'run-spec-converge':
|
|
511
512
|
return `/spec-converge`;
|
|
512
513
|
case 'run-drift-check':
|
|
513
|
-
return `/project drift
|
|
514
|
+
return `/project drift ${projectId} ${roundIndex}`;
|
|
514
515
|
case 'start-round':
|
|
515
516
|
default:
|
|
516
517
|
return `/project run-round ${projectId} ${roundIndex}`;
|
|
@@ -5737,6 +5738,197 @@ export function createRoutes(ctx) {
|
|
|
5737
5738
|
driftInFlight.delete(project.id);
|
|
5738
5739
|
}
|
|
5739
5740
|
});
|
|
5741
|
+
// POST /projects/:id/run-round — manual round-start trigger.
|
|
5742
|
+
//
|
|
5743
|
+
// Spec § Phase 1.7: `/project run-round` is the user-invoked entry path.
|
|
5744
|
+
// Per § Phase 1.5, every entry path goes through `ProjectRoundRunner.preflight`.
|
|
5745
|
+
// On a successful preflight the route schedules the round for the auto-advance
|
|
5746
|
+
// poller by setting `autoAdvanceAt = now`; the poller fires the executor on
|
|
5747
|
+
// its next tick (≤60s). This avoids spawning a long-running child process
|
|
5748
|
+
// from a request handler and keeps a single fire path through the poller.
|
|
5749
|
+
//
|
|
5750
|
+
// Body: { roundIndex: number }
|
|
5751
|
+
// Returns 200 with the preflight verdict + scheduling timestamp on accept,
|
|
5752
|
+
// 409 with the preflight reason on reject, 503 if the runner is not wired.
|
|
5753
|
+
router.post('/projects/:id/run-round', async (req, res) => {
|
|
5754
|
+
const project = lookupProject(req, res);
|
|
5755
|
+
if (!project)
|
|
5756
|
+
return;
|
|
5757
|
+
if (!ctx.initiativeTracker)
|
|
5758
|
+
return;
|
|
5759
|
+
if (!ctx.projectRoundRunner) {
|
|
5760
|
+
res.status(503).json({ error: 'ProjectRoundRunner not configured' });
|
|
5761
|
+
return;
|
|
5762
|
+
}
|
|
5763
|
+
const body = (req.body ?? {});
|
|
5764
|
+
const idx = typeof body.roundIndex === 'number' ? body.roundIndex : 0;
|
|
5765
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
5766
|
+
res.status(400).json({ error: '"roundIndex" must be a non-negative integer' });
|
|
5767
|
+
return;
|
|
5768
|
+
}
|
|
5769
|
+
const rounds = project.rounds ?? [];
|
|
5770
|
+
if (idx >= rounds.length) {
|
|
5771
|
+
res.status(404).json({ error: `round index ${idx} out of range (0..${rounds.length - 1})` });
|
|
5772
|
+
return;
|
|
5773
|
+
}
|
|
5774
|
+
const verdict = ctx.projectRoundRunner.preflight(project.id, idx);
|
|
5775
|
+
if (!verdict.ok) {
|
|
5776
|
+
res.status(409).json({ error: 'preflight rejected', code: verdict.code, reason: verdict.reason });
|
|
5777
|
+
return;
|
|
5778
|
+
}
|
|
5779
|
+
try {
|
|
5780
|
+
const now = new Date().toISOString();
|
|
5781
|
+
const nextRounds = rounds.map((r, i) => (i === idx ? { ...r, autoAdvanceAt: now } : r));
|
|
5782
|
+
const updated = await ctx.initiativeTracker.update(project.id, {
|
|
5783
|
+
rounds: nextRounds,
|
|
5784
|
+
ifMatch: project.version,
|
|
5785
|
+
});
|
|
5786
|
+
res.json({
|
|
5787
|
+
id: updated.id,
|
|
5788
|
+
roundIndex: idx,
|
|
5789
|
+
scheduledAt: now,
|
|
5790
|
+
version: updated.version,
|
|
5791
|
+
});
|
|
5792
|
+
}
|
|
5793
|
+
catch (err) {
|
|
5794
|
+
if (err instanceof Error && err.name === 'OccVersionMismatchError') {
|
|
5795
|
+
const cv = err.currentVersion;
|
|
5796
|
+
res.status(409).json({ error: 'version mismatch', currentVersion: cv });
|
|
5797
|
+
return;
|
|
5798
|
+
}
|
|
5799
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5800
|
+
}
|
|
5801
|
+
});
|
|
5802
|
+
// POST /projects/:id/resume — resume a halted round.
|
|
5803
|
+
//
|
|
5804
|
+
// Spec § Phase 1.5: clears `haltedAt`/`haltReason` on the round and schedules
|
|
5805
|
+
// it for the auto-advance poller. For rounds with status='failed' that have
|
|
5806
|
+
// hit the 3-attempt resume cap (`resumeAttempts >= 3`), the body must include
|
|
5807
|
+
// `{ force: true }`; otherwise the route returns 409. Resetting a `failed`
|
|
5808
|
+
// round also zeroes `resumeAttempts` so the runner gets a fresh budget.
|
|
5809
|
+
//
|
|
5810
|
+
// Body: { roundIndex?: number (default 0), force?: boolean }
|
|
5811
|
+
router.post('/projects/:id/resume', async (req, res) => {
|
|
5812
|
+
const project = lookupProject(req, res);
|
|
5813
|
+
if (!project)
|
|
5814
|
+
return;
|
|
5815
|
+
if (!ctx.initiativeTracker)
|
|
5816
|
+
return;
|
|
5817
|
+
const body = (req.body ?? {});
|
|
5818
|
+
const idx = typeof body.roundIndex === 'number' ? body.roundIndex : 0;
|
|
5819
|
+
const force = body.force === true;
|
|
5820
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
5821
|
+
res.status(400).json({ error: '"roundIndex" must be a non-negative integer' });
|
|
5822
|
+
return;
|
|
5823
|
+
}
|
|
5824
|
+
const rounds = project.rounds ?? [];
|
|
5825
|
+
if (idx >= rounds.length) {
|
|
5826
|
+
res.status(404).json({ error: `round index ${idx} out of range (0..${rounds.length - 1})` });
|
|
5827
|
+
return;
|
|
5828
|
+
}
|
|
5829
|
+
const round = rounds[idx];
|
|
5830
|
+
if (round.status === 'failed' && (round.resumeAttempts ?? 0) >= 3 && !force) {
|
|
5831
|
+
res.status(409).json({
|
|
5832
|
+
error: 'round at resume-attempts cap; pass {force:true} to override',
|
|
5833
|
+
resumeAttempts: round.resumeAttempts,
|
|
5834
|
+
});
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
if (!round.haltedAt && round.status !== 'failed') {
|
|
5838
|
+
res.status(409).json({
|
|
5839
|
+
error: 'round is not halted or failed',
|
|
5840
|
+
status: round.status,
|
|
5841
|
+
});
|
|
5842
|
+
return;
|
|
5843
|
+
}
|
|
5844
|
+
try {
|
|
5845
|
+
const now = new Date().toISOString();
|
|
5846
|
+
const nextRounds = rounds.map((r, i) => {
|
|
5847
|
+
if (i !== idx)
|
|
5848
|
+
return r;
|
|
5849
|
+
const { haltedAt: _h, haltReason: _r, ...rest } = r;
|
|
5850
|
+
const resetAttempts = r.status === 'failed' && force;
|
|
5851
|
+
return {
|
|
5852
|
+
...rest,
|
|
5853
|
+
status: 'pending',
|
|
5854
|
+
autoAdvanceAt: now,
|
|
5855
|
+
resumeAttempts: resetAttempts ? 0 : r.resumeAttempts,
|
|
5856
|
+
};
|
|
5857
|
+
});
|
|
5858
|
+
const nextStatus = project.status === 'halted' || project.status === 'abandoned' ? 'active' : project.status;
|
|
5859
|
+
const updated = await ctx.initiativeTracker.update(project.id, {
|
|
5860
|
+
rounds: nextRounds,
|
|
5861
|
+
status: nextStatus,
|
|
5862
|
+
ifMatch: project.version,
|
|
5863
|
+
});
|
|
5864
|
+
res.json({
|
|
5865
|
+
id: updated.id,
|
|
5866
|
+
roundIndex: idx,
|
|
5867
|
+
scheduledAt: now,
|
|
5868
|
+
forced: force,
|
|
5869
|
+
version: updated.version,
|
|
5870
|
+
});
|
|
5871
|
+
}
|
|
5872
|
+
catch (err) {
|
|
5873
|
+
if (err instanceof Error && err.name === 'OccVersionMismatchError') {
|
|
5874
|
+
const cv = err.currentVersion;
|
|
5875
|
+
res.status(409).json({ error: 'version mismatch', currentVersion: cv });
|
|
5876
|
+
return;
|
|
5877
|
+
}
|
|
5878
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5879
|
+
}
|
|
5880
|
+
});
|
|
5881
|
+
// POST /projects/:id/abandon — archive a halted/failed project.
|
|
5882
|
+
//
|
|
5883
|
+
// Spec § Phase 1.7: "archive a halted round; children remain at current stage."
|
|
5884
|
+
// Sets project.status='abandoned' and clears any future `autoAdvanceAt` on
|
|
5885
|
+
// remaining rounds so the poller stops considering them. Children's
|
|
5886
|
+
// `pipelineStage` is left untouched (per spec). Idempotent.
|
|
5887
|
+
//
|
|
5888
|
+
// Refuses (409) if any round is currently in-progress — halt first.
|
|
5889
|
+
router.post('/projects/:id/abandon', async (req, res) => {
|
|
5890
|
+
const project = lookupProject(req, res);
|
|
5891
|
+
if (!project)
|
|
5892
|
+
return;
|
|
5893
|
+
if (!ctx.initiativeTracker)
|
|
5894
|
+
return;
|
|
5895
|
+
const rounds = project.rounds ?? [];
|
|
5896
|
+
const active = rounds.find((r) => r.status === 'in-progress');
|
|
5897
|
+
if (active) {
|
|
5898
|
+
res.status(409).json({
|
|
5899
|
+
error: 'cannot abandon while a round is in-progress; halt the round first',
|
|
5900
|
+
activeRound: active.name,
|
|
5901
|
+
});
|
|
5902
|
+
return;
|
|
5903
|
+
}
|
|
5904
|
+
if (project.status === 'abandoned') {
|
|
5905
|
+
res.json({ id: project.id, status: 'abandoned', version: project.version, alreadyAbandoned: true });
|
|
5906
|
+
return;
|
|
5907
|
+
}
|
|
5908
|
+
try {
|
|
5909
|
+
const clearedRounds = rounds.map((r) => {
|
|
5910
|
+
if (r.autoAdvanceAt) {
|
|
5911
|
+
const { autoAdvanceAt: _x, ...rest } = r;
|
|
5912
|
+
return rest;
|
|
5913
|
+
}
|
|
5914
|
+
return r;
|
|
5915
|
+
});
|
|
5916
|
+
const updated = await ctx.initiativeTracker.update(project.id, {
|
|
5917
|
+
rounds: clearedRounds,
|
|
5918
|
+
status: 'abandoned',
|
|
5919
|
+
ifMatch: project.version,
|
|
5920
|
+
});
|
|
5921
|
+
res.json({ id: updated.id, status: updated.status, version: updated.version });
|
|
5922
|
+
}
|
|
5923
|
+
catch (err) {
|
|
5924
|
+
if (err instanceof Error && err.name === 'OccVersionMismatchError') {
|
|
5925
|
+
const cv = err.currentVersion;
|
|
5926
|
+
res.status(409).json({ error: 'version mismatch', currentVersion: cv });
|
|
5927
|
+
return;
|
|
5928
|
+
}
|
|
5929
|
+
res.status(400).json({ error: err instanceof Error ? err.message : String(err) });
|
|
5930
|
+
}
|
|
5931
|
+
});
|
|
5740
5932
|
router.post('/projects', async (req, res) => {
|
|
5741
5933
|
if (!ctx.initiativeTracker) {
|
|
5742
5934
|
res.status(503).json({ error: 'Initiative tracker not configured' });
|