scene-capability-engine 3.6.0 → 3.6.3

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.
@@ -13,6 +13,7 @@ const { captureTimelineCheckpoint } = require('../runtime/project-timeline');
13
13
  const { runProblemEvaluation } = require('../problem/problem-evaluator');
14
14
  const { TaskRefRegistry } = require('../task/task-ref-registry');
15
15
  const { getSceStateStore } = require('../state/sce-state-store');
16
+ const { ensureWriteAuthorization } = require('../security/write-authorization');
16
17
  const {
17
18
  loadStudioIntakePolicy,
18
19
  runStudioAutoIntake,
@@ -1336,6 +1337,129 @@ function buildTaskSummaryLines(job = {}, stageName = '', taskStatus = '', nextAc
1336
1337
  ];
1337
1338
  }
1338
1339
 
1340
+ function truncateTaskText(value = '', maxLength = 96) {
1341
+ const normalized = normalizeString(value).replace(/\s+/g, ' ');
1342
+ if (!normalized) {
1343
+ return '';
1344
+ }
1345
+ if (normalized.length <= maxLength) {
1346
+ return normalized;
1347
+ }
1348
+ return `${normalized.slice(0, Math.max(0, maxLength - 3)).trim()}...`;
1349
+ }
1350
+
1351
+ function dedupeTaskList(items = [], limit = 3) {
1352
+ const seen = new Set();
1353
+ const result = [];
1354
+ for (const item of items) {
1355
+ const normalized = truncateTaskText(item, 120);
1356
+ if (!normalized) {
1357
+ continue;
1358
+ }
1359
+ const key = normalized.toLowerCase();
1360
+ if (seen.has(key)) {
1361
+ continue;
1362
+ }
1363
+ seen.add(key);
1364
+ result.push(normalized);
1365
+ if (result.length >= limit) {
1366
+ break;
1367
+ }
1368
+ }
1369
+ return result;
1370
+ }
1371
+
1372
+ function splitTaskRawRequest(rawRequest = '') {
1373
+ const normalized = normalizeString(rawRequest).replace(/\s+/g, ' ');
1374
+ if (!normalized) {
1375
+ return [];
1376
+ }
1377
+ const chunks = normalized
1378
+ .split(/(?:\r?\n|[;;。!?!?]|(?:\s+\band\b\s+)|(?:\s+\bthen\b\s+)|(?:\s+\balso\b\s+)|(?:\s*并且\s*)|(?:\s*同时\s*)|(?:\s*以及\s*)|(?:\s*然后\s*))/gi)
1379
+ .map((item) => normalizeString(item).replace(/^(?:and|then|also)\s+/i, ''))
1380
+ .filter(Boolean);
1381
+ return dedupeTaskList(chunks, 3);
1382
+ }
1383
+
1384
+ function deriveTaskIntentShape(rawRequest = '', stageName = '') {
1385
+ const normalizedRaw = normalizeString(rawRequest).replace(/\s+/g, ' ');
1386
+ const clauses = splitTaskRawRequest(normalizedRaw);
1387
+ const hasRaw = normalizedRaw.length > 0;
1388
+ const inferredSubGoals = clauses.length > 1 ? clauses.slice(0, 3) : [];
1389
+ const needsSplit = inferredSubGoals.length > 1;
1390
+ const titleSource = clauses.length > 0
1391
+ ? clauses[0]
1392
+ : (hasRaw ? normalizedRaw : `Studio ${stageName || 'task'} execution`);
1393
+
1394
+ let confidence = hasRaw ? 0.9 : 0.6;
1395
+ if (needsSplit) {
1396
+ confidence = 0.72;
1397
+ }
1398
+ if (normalizeString(stageName) && normalizeString(stageName) !== 'plan') {
1399
+ confidence = Math.min(0.95, confidence + 0.03);
1400
+ }
1401
+
1402
+ return {
1403
+ title_norm: truncateTaskText(titleSource, 96) || `Studio ${stageName || 'task'} execution`,
1404
+ raw_request: hasRaw ? normalizedRaw : null,
1405
+ sub_goals: inferredSubGoals,
1406
+ needs_split: needsSplit,
1407
+ confidence: Number(confidence.toFixed(2))
1408
+ };
1409
+ }
1410
+
1411
+ function buildTaskAcceptanceCriteria(stageName = '', job = {}, nextAction = '') {
1412
+ const normalizedStage = normalizeString(stageName) || 'task';
1413
+ const artifacts = job && job.artifacts ? job.artifacts : {};
1414
+ const criteriaByStage = {
1415
+ plan: [
1416
+ 'Scene/spec binding is resolved and persisted in studio job metadata.',
1417
+ 'Plan stage problem evaluation passes with no blockers.',
1418
+ `Next action is executable (${nextAction || 'sce studio generate --job <job-id>'}).`
1419
+ ],
1420
+ generate: [
1421
+ 'Patch bundle id is produced for downstream apply stage.',
1422
+ 'Generate stage report is written to artifacts.',
1423
+ `Next action is executable (${nextAction || 'sce studio apply --patch-bundle <id> --job <job-id>'}).`
1424
+ ],
1425
+ apply: [
1426
+ 'Authorization requirements are satisfied for apply stage.',
1427
+ 'Apply stage completes without policy blockers.',
1428
+ `Next action is executable (${nextAction || 'sce studio verify --job <job-id>'}).`
1429
+ ],
1430
+ verify: [
1431
+ 'Verification gates finish with no required-step failures.',
1432
+ `Verify report is available (${normalizeString(artifacts.verify_report) || 'artifact pending'}).`,
1433
+ `Next action is executable (${nextAction || 'sce studio release --job <job-id>'}).`
1434
+ ],
1435
+ release: [
1436
+ 'Release gates pass under configured release profile.',
1437
+ `Release reference is emitted (${normalizeString(artifacts.release_ref) || 'artifact pending'}).`,
1438
+ `Next action is executable (${nextAction || 'complete'}).`
1439
+ ],
1440
+ rollback: [
1441
+ 'Rollback stage transitions job status to rolled_back.',
1442
+ 'Rollback evidence is appended to studio event stream.',
1443
+ `Recovery next action is executable (${nextAction || 'sce studio plan --scene <scene-id> --from-chat <session>'}).`
1444
+ ],
1445
+ events: [
1446
+ 'Events stream payload is available for task-level audit.',
1447
+ 'Task envelope preserves normalized IDs and handoff fields.',
1448
+ `Next action is explicit (${nextAction || 'n/a'}).`
1449
+ ],
1450
+ resume: [
1451
+ 'Current job status and stage progress are restored deterministically.',
1452
+ 'Task envelope remains schema-compatible for downstream UI.',
1453
+ `Next action is explicit (${nextAction || 'n/a'}).`
1454
+ ]
1455
+ };
1456
+ return criteriaByStage[normalizedStage] || [
1457
+ 'Task envelope contains normalized identifiers and task contract fields.',
1458
+ 'Task output preserves evidence, command logs, and error bundles.',
1459
+ `Next action is explicit (${nextAction || 'n/a'}).`
1460
+ ];
1461
+ }
1462
+
1339
1463
  function buildTaskEnvelope(mode, job, options = {}) {
1340
1464
  const stageName = resolveTaskStage(mode, job, options.stageName);
1341
1465
  const stageState = stageName && job && job.stages && job.stages[stageName]
@@ -1359,8 +1483,10 @@ function buildTaskEnvelope(mode, job, options = {}) {
1359
1483
  || (normalizeString(job && job.job_id)
1360
1484
  ? `${job.job_id}:${stageName || 'task'}`
1361
1485
  : null);
1362
- const goal = normalizeString(job?.source?.goal)
1486
+ const rawRequest = normalizeString(job?.source?.goal);
1487
+ const goal = rawRequest
1363
1488
  || `Studio ${stageName || 'task'} execution`;
1489
+ const taskIntent = deriveTaskIntentShape(rawRequest, stageName);
1364
1490
  const sessionId = normalizeString(job?.session?.scene_session_id) || null;
1365
1491
  const sceneId = normalizeString(job?.scene?.id) || null;
1366
1492
  const specId = normalizeString(job?.scene?.spec_id) || normalizeString(job?.source?.spec_id) || null;
@@ -1400,7 +1526,14 @@ function buildTaskEnvelope(mode, job, options = {}) {
1400
1526
  eventId: normalizeString(latestEvent && latestEvent.event_id) || null,
1401
1527
  task: {
1402
1528
  ref: taskRef,
1529
+ task_ref: taskRef,
1530
+ title_norm: taskIntent.title_norm,
1531
+ raw_request: taskIntent.raw_request,
1403
1532
  goal,
1533
+ sub_goals: taskIntent.sub_goals,
1534
+ acceptance_criteria: buildTaskAcceptanceCriteria(stageName, job, nextAction),
1535
+ needs_split: taskIntent.needs_split,
1536
+ confidence: taskIntent.confidence,
1404
1537
  status: taskStatus,
1405
1538
  summary: buildTaskSummaryLines(job, stageName, taskStatus, nextAction, taskRef),
1406
1539
  handoff: normalizedHandoff,
@@ -2529,6 +2662,12 @@ async function runStudioApplyCommand(options = {}, dependencies = {}) {
2529
2662
  const job = await loadJob(paths, jobId, fileSystem);
2530
2663
  ensureNotRolledBack(job, 'apply');
2531
2664
  ensureStagePrerequisite(job, 'apply', 'generate');
2665
+ const leaseAuthResult = await ensureWriteAuthorization('studio:apply', options, {
2666
+ projectPath,
2667
+ fileSystem,
2668
+ env: dependencies.env,
2669
+ authSecret: dependencies.authSecret
2670
+ });
2532
2671
  const authResult = await ensureStudioAuthorization('apply', options, {
2533
2672
  projectPath,
2534
2673
  fileSystem,
@@ -2564,14 +2703,22 @@ async function runStudioApplyCommand(options = {}, dependencies = {}) {
2564
2703
 
2565
2704
  ensureStageCompleted(job, 'apply', {
2566
2705
  patch_bundle_id: patchBundleId,
2567
- auth_required: authResult.required,
2706
+ auth_required: authResult.required || leaseAuthResult.required,
2707
+ auth_password_required: authResult.required,
2708
+ auth_lease_required: leaseAuthResult.required,
2709
+ auth_lease_id: leaseAuthResult.lease_id || null,
2710
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2568
2711
  problem_evaluation: summarizeProblemEvaluation(applyProblemEvaluation)
2569
2712
  });
2570
2713
 
2571
2714
  await saveJob(paths, job, fileSystem);
2572
2715
  const applyEvent = await appendStudioEvent(paths, job, 'stage.apply.completed', {
2573
2716
  patch_bundle_id: patchBundleId,
2574
- auth_required: authResult.required,
2717
+ auth_required: authResult.required || leaseAuthResult.required,
2718
+ auth_password_required: authResult.required,
2719
+ auth_lease_required: leaseAuthResult.required,
2720
+ auth_lease_id: leaseAuthResult.lease_id || null,
2721
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2575
2722
  problem_evaluation: summarizeProblemEvaluation(applyProblemEvaluation)
2576
2723
  }, fileSystem);
2577
2724
  await writeLatestJob(paths, jobId, fileSystem);
@@ -2758,6 +2905,12 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
2758
2905
  const job = await loadJob(paths, jobId, fileSystem);
2759
2906
  ensureNotRolledBack(job, 'release');
2760
2907
  ensureStagePrerequisite(job, 'release', 'verify');
2908
+ const leaseAuthResult = await ensureWriteAuthorization('studio:release', options, {
2909
+ projectPath,
2910
+ fileSystem,
2911
+ env: dependencies.env,
2912
+ authSecret: dependencies.authSecret
2913
+ });
2761
2914
  const authResult = await ensureStudioAuthorization('release', options, {
2762
2915
  projectPath,
2763
2916
  fileSystem,
@@ -2874,7 +3027,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
2874
3027
  passed: false,
2875
3028
  report: releaseReportPath,
2876
3029
  gate_steps: gateResult.steps,
2877
- auth_required: authResult.required,
3030
+ auth_required: authResult.required || leaseAuthResult.required,
3031
+ auth_password_required: authResult.required,
3032
+ auth_lease_required: leaseAuthResult.required,
3033
+ auth_lease_id: leaseAuthResult.lease_id || null,
3034
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2878
3035
  problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
2879
3036
  domain_chain: domainChainMetadata,
2880
3037
  auto_errorbook_records: autoErrorbookRecords
@@ -2885,7 +3042,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
2885
3042
  channel,
2886
3043
  release_ref: releaseRef,
2887
3044
  report: releaseReportPath,
2888
- auth_required: authResult.required,
3045
+ auth_required: authResult.required || leaseAuthResult.required,
3046
+ auth_password_required: authResult.required,
3047
+ auth_lease_required: leaseAuthResult.required,
3048
+ auth_lease_id: leaseAuthResult.lease_id || null,
3049
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2889
3050
  problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
2890
3051
  domain_chain: domainChainMetadata,
2891
3052
  auto_errorbook_records: autoErrorbookRecords
@@ -2900,7 +3061,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
2900
3061
  release_ref: releaseRef,
2901
3062
  report: releaseReportPath,
2902
3063
  gate_steps: gateResult.steps,
2903
- auth_required: authResult.required,
3064
+ auth_required: authResult.required || leaseAuthResult.required,
3065
+ auth_password_required: authResult.required,
3066
+ auth_lease_required: leaseAuthResult.required,
3067
+ auth_lease_id: leaseAuthResult.lease_id || null,
3068
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2904
3069
  problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
2905
3070
  domain_chain: domainChainMetadata,
2906
3071
  auto_errorbook_records: autoErrorbookRecords
@@ -2932,7 +3097,11 @@ async function runStudioReleaseCommand(options = {}, dependencies = {}) {
2932
3097
  channel,
2933
3098
  release_ref: releaseRef,
2934
3099
  report: releaseReportPath,
2935
- auth_required: authResult.required,
3100
+ auth_required: authResult.required || leaseAuthResult.required,
3101
+ auth_password_required: authResult.required,
3102
+ auth_lease_required: leaseAuthResult.required,
3103
+ auth_lease_id: leaseAuthResult.lease_id || null,
3104
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null,
2936
3105
  problem_evaluation: summarizeProblemEvaluation(releaseProblemEvaluation),
2937
3106
  domain_chain: domainChainMetadata,
2938
3107
  auto_errorbook_records: autoErrorbookRecords
@@ -2987,6 +3156,12 @@ async function runStudioRollbackCommand(options = {}, dependencies = {}) {
2987
3156
 
2988
3157
  const reason = normalizeString(options.reason) || 'manual-rollback';
2989
3158
  const job = await loadJob(paths, jobId, fileSystem);
3159
+ const leaseAuthResult = await ensureWriteAuthorization('studio:rollback', options, {
3160
+ projectPath,
3161
+ fileSystem,
3162
+ env: dependencies.env,
3163
+ authSecret: dependencies.authSecret
3164
+ });
2990
3165
  const authResult = await ensureStudioAuthorization('rollback', options, {
2991
3166
  projectPath,
2992
3167
  fileSystem,
@@ -3002,7 +3177,11 @@ async function runStudioRollbackCommand(options = {}, dependencies = {}) {
3002
3177
  job.rollback = {
3003
3178
  reason,
3004
3179
  rolled_back_at: job.updated_at,
3005
- auth_required: authResult.required
3180
+ auth_required: authResult.required || leaseAuthResult.required,
3181
+ auth_password_required: authResult.required,
3182
+ auth_lease_required: leaseAuthResult.required,
3183
+ auth_lease_id: leaseAuthResult.lease_id || null,
3184
+ auth_lease_expires_at: leaseAuthResult.lease_expires_at || null
3006
3185
  };
3007
3186
 
3008
3187
  const sceneSessionId = normalizeString(job && job.session && job.session.scene_session_id);
@@ -3376,6 +3555,7 @@ function registerStudioCommands(program) {
3376
3555
  .command('apply')
3377
3556
  .description('Apply generated patch bundle metadata to studio job')
3378
3557
  .option('--patch-bundle <id>', 'Patch bundle identifier (defaults to generated artifact)')
3558
+ .option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
3379
3559
  .option('--auth-password <password>', 'Authorization password for protected apply action')
3380
3560
  .option('--require-auth', 'Require authorization even when policy is advisory')
3381
3561
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
@@ -3395,6 +3575,7 @@ function registerStudioCommands(program) {
3395
3575
  .description('Record release stage for studio job')
3396
3576
  .option('--channel <channel>', 'Release channel (dev|prod)', 'dev')
3397
3577
  .option('--profile <profile>', 'Release gate profile', 'standard')
3578
+ .option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
3398
3579
  .option('--auth-password <password>', 'Authorization password for protected release action')
3399
3580
  .option('--require-auth', 'Require authorization even when policy is advisory')
3400
3581
  .option('--release-ref <ref>', 'Explicit release reference/tag')
@@ -3423,6 +3604,7 @@ function registerStudioCommands(program) {
3423
3604
  .description('Rollback a studio job after apply/release')
3424
3605
  .option('--job <job-id>', 'Studio job id (defaults to latest)')
3425
3606
  .option('--reason <reason>', 'Rollback reason')
3607
+ .option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
3426
3608
  .option('--auth-password <password>', 'Authorization password for protected rollback action')
3427
3609
  .option('--require-auth', 'Require authorization even when policy is advisory')
3428
3610
  .option('--json', 'Print machine-readable JSON output')
@@ -11,6 +11,7 @@ const chalk = require('chalk');
11
11
  const TaskClaimer = require('../task/task-claimer');
12
12
  const WorkspaceManager = require('../workspace/workspace-manager');
13
13
  const { TaskRefRegistry } = require('../task/task-ref-registry');
14
+ const { ensureWriteAuthorization } = require('../security/write-authorization');
14
15
 
15
16
  function normalizeString(value) {
16
17
  if (typeof value !== 'string') {
@@ -436,6 +437,17 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
436
437
  if (!lookup) {
437
438
  throw new Error(`Task ref not found: ${taskRef}`);
438
439
  }
440
+ const writeAuthResult = dryRun
441
+ ? {
442
+ required: false,
443
+ passed: true
444
+ }
445
+ : await ensureWriteAuthorization('task:rerun', options, {
446
+ projectPath,
447
+ fileSystem,
448
+ env: dependencies.env,
449
+ authSecret: dependencies.authSecret
450
+ });
439
451
 
440
452
  if (isStudioTaskRef(lookup)) {
441
453
  const stage = resolveStudioStageFromTaskKey(lookup.task_key);
@@ -453,7 +465,12 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
453
465
  stage,
454
466
  job_id: normalizeString(job?.job_id) || null,
455
467
  dry_run: dryRun,
456
- command: stringifySceArgs(rerunPlan.args)
468
+ command: stringifySceArgs(rerunPlan.args),
469
+ authorization: {
470
+ required: writeAuthResult.required === true,
471
+ lease_id: writeAuthResult.lease_id || null,
472
+ lease_expires_at: writeAuthResult.lease_expires_at || null
473
+ }
457
474
  };
458
475
 
459
476
  if (!dryRun) {
@@ -486,7 +503,12 @@ async function runTaskRerunCommand(options = {}, dependencies = {}) {
486
503
  task_ref: lookup.task_ref,
487
504
  rerun_type: 'spec-task',
488
505
  dry_run: dryRun,
489
- command: `sce task claim ${lookup.spec_id} ${lookup.task_key}`
506
+ command: `sce task claim ${lookup.spec_id} ${lookup.task_key}`,
507
+ authorization: {
508
+ required: writeAuthResult.required === true,
509
+ lease_id: writeAuthResult.lease_id || null,
510
+ lease_expires_at: writeAuthResult.lease_expires_at || null
511
+ }
490
512
  };
491
513
 
492
514
  if (!dryRun) {
@@ -734,6 +756,7 @@ function registerTaskCommands(program) {
734
756
  .description('Rerun task by hierarchical reference')
735
757
  .requiredOption('--ref <task-ref>', 'Task reference (SS.PP.TT)')
736
758
  .option('--dry-run', 'Preview rerun command without executing')
759
+ .option('--auth-lease <lease-id>', 'Write authorization lease id (sce auth grant)')
737
760
  .option('--from-chat <session>', 'Override session for studio plan rerun')
738
761
  .option('--job <job-id>', 'Override studio job id')
739
762
  .option('--profile <profile>', 'Override profile for studio verify/release rerun')