easy-coding-harness 0.10.0-beta.8 → 0.10.0-beta.9

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
@@ -6,6 +6,22 @@
6
6
  - `y`:常规功能升级
7
7
  - `z`:日常 bug 修复
8
8
 
9
+ ## 0.10.0-beta.9
10
+
11
+ - 工作流 owner 改为规范平台身份边界:每份安装后的状态脚本固化宿主身份,新写入只接受
12
+ `claude-code` / `codex` / `qoder`,脚本身份、`--agent` 与已注入 session 命名空间三方
13
+ 不一致时在落盘前拒绝;`Codex with Easy Coding` 等作者/Canonical 展示归属不再允许进入
14
+ `last_agent`。
15
+ - Codex 与 Qoder 共用的 `AGENTS.md` 生成区改为安装顺序无关的双平台路径合同,
16
+ 明确当前宿主只能调用自己的状态脚本,防止 Qoder 最后写入后误导 Codex 执行
17
+ `.qoder/hooks/easy_coding_state.py`。
18
+ - handoff 提示改为显式协调事件驱动:只有未消费的 `handoff` 记录才显示交接,
19
+ `claim-task` 新增 immutable `claim` 审计记录并关闭交接;单纯 owner 差异不再被伪造成
20
+ handoff。
21
+ - upgrade 与首次 SessionStart 幂等迁移旧任务/session 中的 `root` 系列和误写展示名,
22
+ 即使版本号已经更新也能继续检测并自愈,但保留 `execution.jsonl` 及 Canonical Spec 的
23
+ 历史审计展示;新增生产形态、跨平台路径冲突、安装顺序和真实 handoff/claim 回归测试。
24
+
9
25
  ## 0.10.0-beta.8
10
26
 
11
27
  - VERIFICATION 绿色后新增可审计验收检查点。无漂移时 `confirm` / `auto` 继续按原审批模式
package/dist/cli.js CHANGED
@@ -311,6 +311,7 @@ var PLATFORM_META = {
311
311
  stateInjectEvent: ["SessionStart", "UserPromptSubmit"],
312
312
  hasSubagentContext: true,
313
313
  templateContext: {
314
+ workflow_agent_id: "claude-code",
314
315
  sub_agent_dispatch: "Agent tool",
315
316
  platform_spawn_instruction: 'Use the Agent tool with run_in_background when useful; use isolation: "worktree" for parallel file edits.',
316
317
  skill_trigger: "/",
@@ -334,6 +335,7 @@ var PLATFORM_META = {
334
335
  stateInjectEvent: ["SessionStart", "UserPromptSubmit"],
335
336
  hasSubagentContext: false,
336
337
  templateContext: {
338
+ workflow_agent_id: "codex",
337
339
  sub_agent_dispatch: "Codex sub-agent dispatch",
338
340
  platform_spawn_instruction: "Use Codex sub-agent delegation where available; pass the full task card in the prompt.",
339
341
  skill_trigger: "$",
@@ -358,6 +360,7 @@ var PLATFORM_META = {
358
360
  hasSubagentContext: true,
359
361
  cnVariant: ".qodercn",
360
362
  templateContext: {
363
+ workflow_agent_id: "qoder",
361
364
  sub_agent_dispatch: "Agent tool",
362
365
  platform_spawn_instruction: "Use the Agent tool with worktree isolation for parallel file edits.",
363
366
  skill_trigger: "/",
@@ -776,6 +779,87 @@ function isLegacyStage(value) {
776
779
  function migrateStage(value) {
777
780
  return isLegacyStage(value) ? LEGACY_STAGE_MAP[value] : value;
778
781
  }
782
+ var LEGACY_DISPLAY_AGENT_IDENTITIES = {
783
+ "claude with easy coding": "claude-code",
784
+ "claude-code with easy coding": "claude-code",
785
+ "claude code with easy coding": "claude-code",
786
+ "codex with easy coding": "codex",
787
+ "qoder with easy coding": "qoder"
788
+ };
789
+ function migratedAgentIdentity(value) {
790
+ if (typeof value !== "string") return void 0;
791
+ const normalized = value.trim().toLowerCase();
792
+ if (normalized === "claude-code" || normalized === "codex" || normalized === "qoder") {
793
+ return normalized;
794
+ }
795
+ if (/^\/?root(?:\/[a-z0-9._-]+)*$/.test(normalized)) return "codex";
796
+ return LEGACY_DISPLAY_AGENT_IDENTITIES[normalized];
797
+ }
798
+ function migrateAgentFields(record, fields) {
799
+ let changed = false;
800
+ for (const field of fields) {
801
+ const migrated = migratedAgentIdentity(record[field]);
802
+ if (migrated && migrated !== record[field]) {
803
+ record[field] = migrated;
804
+ changed = true;
805
+ }
806
+ }
807
+ return changed;
808
+ }
809
+ function taskAgentFieldGroups(task) {
810
+ const groups = [
811
+ {
812
+ record: task,
813
+ fields: ["created_by", "last_agent", "workflow_mode_confirmed_by", "tdd_confirmed_by"]
814
+ }
815
+ ];
816
+ for (const field of ["pending_transition", "workflow_mode_proposal", "verification_checkpoint"]) {
817
+ const value = task[field];
818
+ if (!value || typeof value !== "object" || Array.isArray(value)) continue;
819
+ const identityField = field === "pending_transition" ? "requested_by" : field === "workflow_mode_proposal" ? "proposed_by" : "recorded_by";
820
+ groups.push({ record: value, fields: [identityField] });
821
+ }
822
+ if (Array.isArray(task.workflow_mode_escalations)) {
823
+ for (const escalation of task.workflow_mode_escalations) {
824
+ if (!escalation || typeof escalation !== "object" || Array.isArray(escalation)) continue;
825
+ groups.push({ record: escalation, fields: ["raised_by"] });
826
+ }
827
+ }
828
+ const memoryProgress = task.memory_progress;
829
+ if (memoryProgress && typeof memoryProgress === "object" && !Array.isArray(memoryProgress)) {
830
+ const assessment = memoryProgress.architecture_assessment;
831
+ if (assessment && typeof assessment === "object" && !Array.isArray(assessment)) {
832
+ groups.push({ record: assessment, fields: ["recorded_by"] });
833
+ }
834
+ }
835
+ if (Array.isArray(task.spec_dependency_evidence)) {
836
+ for (const evidence of task.spec_dependency_evidence) {
837
+ if (!evidence || typeof evidence !== "object" || Array.isArray(evidence)) continue;
838
+ groups.push({ record: evidence, fields: ["satisfied_by"] });
839
+ }
840
+ }
841
+ if (Array.isArray(task.stage_history)) {
842
+ for (const entry of task.stage_history) {
843
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
844
+ groups.push({ record: entry, fields: ["agent"] });
845
+ }
846
+ }
847
+ return groups;
848
+ }
849
+ function migrateTaskAgentIdentities(task) {
850
+ return taskAgentFieldGroups(task).reduce(
851
+ (changed, group) => migrateAgentFields(group.record, group.fields) || changed,
852
+ false
853
+ );
854
+ }
855
+ function hasLegacyTaskAgentIdentities(task) {
856
+ return taskAgentFieldGroups(task).some(
857
+ ({ record, fields }) => fields.some((field) => {
858
+ const migrated = migratedAgentIdentity(record[field]);
859
+ return migrated !== void 0 && migrated !== record[field];
860
+ })
861
+ );
862
+ }
779
863
  function migrateStageHistory(task) {
780
864
  if (!Array.isArray(task.stage_history)) return false;
781
865
  let changed = false;
@@ -806,7 +890,8 @@ function migrateTaskWorkflowState(task) {
806
890
  const legacyRequestedAt = Array.isArray(task.stage_history) ? [...task.stage_history].reverse().find(
807
891
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && entry.stage === "WAITING_CONFIRM"
808
892
  ) : null;
809
- let changed = migrateStageHistory(task);
893
+ let changed = migrateTaskAgentIdentities(task);
894
+ changed = migrateStageHistory(task) || changed;
810
895
  if (legacyStatus) {
811
896
  task.status = LEGACY_STAGE_MAP[legacyStatus];
812
897
  changed = true;
@@ -858,7 +943,7 @@ function migrateTaskWorkflowState(task) {
858
943
  return changed;
859
944
  }
860
945
  function migrateSessionBehavior(session) {
861
- let changed = false;
946
+ let changed = migrateAgentFields(session, ["agent", "last_agent"]);
862
947
  const legacyMode = session.confirm_mode;
863
948
  const legacyLite = legacyMode === "lite";
864
949
  if (!["approve", "guard", "confirm", "auto"].includes(String(session.approval_mode ?? ""))) {
@@ -969,7 +1054,7 @@ async function hasLegacyWorkflowState(cwd) {
969
1054
  if (!await pathExists(filePath)) continue;
970
1055
  const task = await readJsonRecord(filePath);
971
1056
  if (!task) continue;
972
- if (isLegacyStage(task.status) || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && String(task.type ?? "") !== "project-init" && !["fast", "standard", "strict"].includes(String(task.workflow_mode ?? "")) || Array.isArray(task.stage_history) && task.stage_history.some(
1057
+ if (hasLegacyTaskAgentIdentities(task) || isLegacyStage(task.status) || !["PENDING", "COMPLETE", "CLOSED"].includes(String(task.status ?? "")) && String(task.type ?? "") !== "project-init" && !["fast", "standard", "strict"].includes(String(task.workflow_mode ?? "")) || Array.isArray(task.stage_history) && task.stage_history.some(
973
1058
  (entry) => entry && typeof entry === "object" && !Array.isArray(entry) && isLegacyStage(entry.stage)
974
1059
  )) {
975
1060
  return true;
@@ -978,7 +1063,12 @@ async function hasLegacyWorkflowState(cwd) {
978
1063
  for (const filePath of await sessionFiles(cwd)) {
979
1064
  const session = await readJsonRecord(filePath);
980
1065
  if (!session) continue;
981
- if (isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) return true;
1066
+ if (["agent", "last_agent"].some((field) => {
1067
+ const migrated = migratedAgentIdentity(session[field]);
1068
+ return migrated !== void 0 && migrated !== session[field];
1069
+ }) || isLegacyStage(session.last_seen_stage) || "confirm_mode" in session) {
1070
+ return true;
1071
+ }
982
1072
  }
983
1073
  return false;
984
1074
  }