@mokuyoaxis/dsh-iris 0.1.2 → 0.1.4
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/.dsh/skills/iris-compose-media/SKILL.md +23 -49
- package/.dsh/skills/iris-compose-media/references/task-v2-recovery.md +30 -0
- package/.dsh/skills/iris-compose-media/references/workflows.md +38 -0
- package/.dsh/skills/iris-verify-ui/SKILL.md +3 -25
- package/.dsh/skills/iris-verify-ui/agents/openai.yaml +4 -0
- package/.dsh/skills/iris-verify-ui/references/verdict-and-degradation.md +31 -0
- package/CHANGELOG.md +57 -2
- package/README.en.md +43 -7
- package/README.md +43 -7
- package/bin/dsh-iris.js +23 -0
- package/docs/ARCHITECTURE.md +95 -0
- package/docs/ARTIFACT_LIBRARY.md +24 -0
- package/docs/FAULT_INJECTION.md +26 -0
- package/docs/HOST_ADAPTER_CONTRACT.md +86 -0
- package/docs/HOST_DOCTOR.md +47 -0
- package/docs/PROVIDER_ADAPTER_CONTRACT.md +97 -0
- package/docs/PROVIDER_HEALTH.md +39 -0
- package/docs/PROVIDER_SUBMISSION_CONTRACT.md +60 -0
- package/docs/ROADMAP.md +21 -11
- package/docs/SECURITY.md +57 -0
- package/docs/TASK_SEMANTICS.md +148 -0
- package/docs/assets/diagrams/iris-architecture.png +0 -0
- package/docs/assets/diagrams/iris-architecture.svg +5 -5
- package/docs/releases/0.1.4.md +59 -0
- package/lib/actions.js +860 -290
- package/lib/adapters.js +7 -7
- package/lib/api.js +110 -18
- package/lib/artifacts.js +239 -0
- package/lib/bundled-skills.js +5 -5
- package/lib/client.js +478 -68
- package/lib/config.js +192 -9
- package/lib/doctor.js +414 -0
- package/lib/dsh-host-adapter.js +189 -0
- package/lib/host-contract.js +131 -0
- package/lib/host-runtime.js +156 -0
- package/lib/index.js +142 -157
- package/lib/locate.js +1 -1
- package/lib/media.js +16 -6
- package/lib/prompt-optimizer.js +10 -20
- package/lib/provider-adapter.js +371 -0
- package/lib/provider-adapters.js +279 -0
- package/lib/provider-contract.js +234 -0
- package/lib/provider-health.js +59 -0
- package/lib/render.js +43 -12
- package/lib/task-semantics.js +209 -0
- package/lib/tasks.js +500 -32
- package/lib/vision.js +23 -49
- package/package.json +19 -8
- package/user_guide.md +62 -2
- package/docs/assets/diagrams/iris-architecture.drawio +0 -1
- package/docs/assets/diagrams/iris-task-lifecycle.drawio +0 -1
- package/docs/assets/diagrams/iris-workflow-compose.drawio +0 -1
- package/docs/releases/0.1.1.md +0 -51
- package/docs/releases/0.1.2.md +0 -26
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Iris Task/Attempt v2 的纯语义契约。
|
|
4
|
+
*
|
|
5
|
+
* 本模块只定义名称、派生规则和不可违反的约束,不直接读写任务或调用供应商;
|
|
6
|
+
* tasks、API 与 Doctor 共同消费这里的语义,避免各自解释状态。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const TASK_PHASES = Object.freeze(['queued', 'submitting', 'accepted', 'running', 'terminal']);
|
|
10
|
+
export const ACCEPTANCE_STATES = Object.freeze(['none', 'not_accepted', 'accepted', 'unknown']);
|
|
11
|
+
export const WATCH_STATES = Object.freeze(['idle', 'active', 'suspended', 'exhausted']);
|
|
12
|
+
export const TASK_OUTCOMES = Object.freeze(['none', 'succeeded', 'failed', 'canceled', 'unknown']);
|
|
13
|
+
export const DELIVERY_STATES = Object.freeze(['none', 'pending', 'downloading', 'ready', 'failed']);
|
|
14
|
+
export const CANCEL_STATES = Object.freeze(['none', 'requested', 'remote_confirmed', 'local_confirmed', 'unknown']);
|
|
15
|
+
|
|
16
|
+
export const USER_STATES = Object.freeze([
|
|
17
|
+
'queued',
|
|
18
|
+
'running',
|
|
19
|
+
'watching_paused',
|
|
20
|
+
'needs_attention',
|
|
21
|
+
'artifact_unavailable',
|
|
22
|
+
'succeeded',
|
|
23
|
+
'failed',
|
|
24
|
+
'canceled'
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
const includes = (values, value) => values.includes(value);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 自动 failover 的唯一许可条件。
|
|
31
|
+
* retryable、HTTP 状态或网络错误本身都不能推翻受理边界。
|
|
32
|
+
*/
|
|
33
|
+
export function allowsAutomaticFailover(attempt) {
|
|
34
|
+
return Boolean(attempt && attempt.acceptance === 'not_accepted');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 给仍只理解 running/succeeded/failed/canceled 的旧消费者提供保守视图。
|
|
39
|
+
* 无法准确表达的未知、观察暂停和交付失败一律不伪装成终态。
|
|
40
|
+
*/
|
|
41
|
+
export function deriveLegacyStatus(task) {
|
|
42
|
+
if (!task || typeof task !== 'object') return 'running';
|
|
43
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'ready') return 'succeeded';
|
|
44
|
+
if (task.outcome === 'failed') return 'failed';
|
|
45
|
+
if (
|
|
46
|
+
task.outcome === 'canceled' &&
|
|
47
|
+
(task.cancelState === 'remote_confirmed' || task.cancelState === 'local_confirmed')
|
|
48
|
+
) return 'canceled';
|
|
49
|
+
return 'running';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** 面向工作台的稳定语义;UI 文案可以本地化,但不能改变事实分类。 */
|
|
53
|
+
export function deriveUserState(task) {
|
|
54
|
+
if (!task || typeof task !== 'object') return 'needs_attention';
|
|
55
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'ready') return 'succeeded';
|
|
56
|
+
if (task.outcome === 'succeeded' && task.deliveryState === 'failed') return 'artifact_unavailable';
|
|
57
|
+
if (task.outcome === 'failed') return 'failed';
|
|
58
|
+
if (
|
|
59
|
+
task.outcome === 'canceled' &&
|
|
60
|
+
(task.cancelState === 'remote_confirmed' || task.cancelState === 'local_confirmed')
|
|
61
|
+
) return 'canceled';
|
|
62
|
+
if (
|
|
63
|
+
task.acceptance === 'unknown' ||
|
|
64
|
+
task.outcome === 'unknown' ||
|
|
65
|
+
task.cancelState === 'unknown'
|
|
66
|
+
) return 'needs_attention';
|
|
67
|
+
if (task.watchState === 'suspended' || task.watchState === 'exhausted') return 'watching_paused';
|
|
68
|
+
if (task.phase === 'queued') return 'queued';
|
|
69
|
+
return 'running';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 提醒处置是事实轴之外的用户工作流视图。事件优先;早期 0.1.3 只有
|
|
74
|
+
* manualRetries 的记录也应视为已通过重试处理,避免升级后重复提醒。
|
|
75
|
+
*/
|
|
76
|
+
export function attentionDisposition(task) {
|
|
77
|
+
const events = Array.isArray(task && task.attentionEvents) ? task.attentionEvents : [];
|
|
78
|
+
const last = [...events].reverse().find((item) => item && ['acknowledged', 'restored'].includes(item.type));
|
|
79
|
+
if (last) {
|
|
80
|
+
return {
|
|
81
|
+
status: last.type === 'acknowledged' ? 'acknowledged' : 'open',
|
|
82
|
+
reason: last.reason || (last.type === 'restored' ? 'restored' : 'read'),
|
|
83
|
+
updatedAt: last.at || '',
|
|
84
|
+
...(last.relatedTaskId ? { relatedTaskId: last.relatedTaskId } : {})
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const retries = Array.isArray(task && task.manualRetries) ? task.manualRetries : [];
|
|
88
|
+
const retry = retries[retries.length - 1];
|
|
89
|
+
if (retry && retry.taskId) {
|
|
90
|
+
return {
|
|
91
|
+
status: 'acknowledged', reason: 'retried', updatedAt: retry.createdAt || '',
|
|
92
|
+
relatedTaskId: retry.taskId, inferred: true
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
return { status: 'open', reason: '', updatedAt: '' };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 校验 v2 记录内部是否自洽。返回稳定错误码,调用方决定如何展示。
|
|
100
|
+
* 这里只检查不可违反的事实关系,不替持久化层补默认值。
|
|
101
|
+
*/
|
|
102
|
+
export function semanticViolations(task) {
|
|
103
|
+
const errors = [];
|
|
104
|
+
if (!task || typeof task !== 'object' || Array.isArray(task)) return ['task.object'];
|
|
105
|
+
|
|
106
|
+
if (!includes(TASK_PHASES, task.phase)) errors.push('task.phase');
|
|
107
|
+
if (!includes(ACCEPTANCE_STATES, task.acceptance)) errors.push('task.acceptance');
|
|
108
|
+
if (!includes(WATCH_STATES, task.watchState)) errors.push('task.watchState');
|
|
109
|
+
if (!includes(TASK_OUTCOMES, task.outcome)) errors.push('task.outcome');
|
|
110
|
+
if (!includes(DELIVERY_STATES, task.deliveryState)) errors.push('task.deliveryState');
|
|
111
|
+
if (!includes(CANCEL_STATES, task.cancelState)) errors.push('task.cancelState');
|
|
112
|
+
|
|
113
|
+
if (task.watchState === 'active' && task.acceptance !== 'accepted') {
|
|
114
|
+
errors.push('watch.active_requires_acceptance');
|
|
115
|
+
}
|
|
116
|
+
if (task.phase === 'terminal' && task.watchState === 'active') {
|
|
117
|
+
errors.push('terminal.active_watch');
|
|
118
|
+
}
|
|
119
|
+
if (task.deliveryState !== 'none' && task.outcome !== 'succeeded') {
|
|
120
|
+
errors.push('delivery.requires_success');
|
|
121
|
+
}
|
|
122
|
+
if (
|
|
123
|
+
(task.cancelState === 'remote_confirmed' || task.cancelState === 'local_confirmed') &&
|
|
124
|
+
task.outcome !== 'canceled'
|
|
125
|
+
) {
|
|
126
|
+
errors.push('cancel.confirmed_requires_canceled_outcome');
|
|
127
|
+
}
|
|
128
|
+
if (
|
|
129
|
+
task.outcome === 'canceled' &&
|
|
130
|
+
task.cancelState !== 'remote_confirmed' &&
|
|
131
|
+
task.cancelState !== 'local_confirmed'
|
|
132
|
+
) {
|
|
133
|
+
errors.push('canceled.requires_confirmation');
|
|
134
|
+
}
|
|
135
|
+
return errors;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const V2_FIELDS = ['phase', 'acceptance', 'watchState', 'outcome', 'deliveryState', 'cancelState'];
|
|
139
|
+
const DELIVERY_FAILURE = /转存失败|下载[^::]*失败|落盘失败|delivery/i;
|
|
140
|
+
const WATCH_FAILURE = /盯守超时|轮询连续失败|无法恢复|任务已超时/;
|
|
141
|
+
|
|
142
|
+
function hasCompleteV2Shape(task) {
|
|
143
|
+
return V2_FIELDS.every((field) => Object.prototype.hasOwnProperty.call(task, field));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 将旧四态任务转换为保守的 v2 内存视图,不修改输入,也不执行磁盘写入。
|
|
148
|
+
* 无法从旧记录证明的受理、结果与取消事实一律使用 unknown。
|
|
149
|
+
*/
|
|
150
|
+
export function normalizeLegacyTask(task) {
|
|
151
|
+
if (!task || typeof task !== 'object' || Array.isArray(task)) {
|
|
152
|
+
throw new TypeError('旧任务必须是对象');
|
|
153
|
+
}
|
|
154
|
+
const out = {
|
|
155
|
+
...task,
|
|
156
|
+
...(Array.isArray(task.attempts) ? { attempts: task.attempts.map((attempt) => ({ ...attempt })) } : {})
|
|
157
|
+
};
|
|
158
|
+
if (hasCompleteV2Shape(out)) return out;
|
|
159
|
+
|
|
160
|
+
const status = String(task.status || 'running');
|
|
161
|
+
const hasRemoteId = typeof task.remoteTaskId === 'string' && Boolean(task.remoteTaskId.trim());
|
|
162
|
+
const error = String(task.error || '');
|
|
163
|
+
const defaults = {
|
|
164
|
+
phase: 'terminal',
|
|
165
|
+
acceptance: hasRemoteId ? 'accepted' : 'unknown',
|
|
166
|
+
watchState: 'idle',
|
|
167
|
+
outcome: 'unknown',
|
|
168
|
+
deliveryState: 'none',
|
|
169
|
+
cancelState: 'none'
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (status === 'succeeded') {
|
|
173
|
+
Object.assign(defaults, {
|
|
174
|
+
acceptance: 'accepted',
|
|
175
|
+
outcome: 'succeeded',
|
|
176
|
+
deliveryState: 'ready'
|
|
177
|
+
});
|
|
178
|
+
} else if (status === 'running' && hasRemoteId) {
|
|
179
|
+
Object.assign(defaults, {
|
|
180
|
+
phase: 'running',
|
|
181
|
+
acceptance: 'accepted',
|
|
182
|
+
watchState: 'suspended',
|
|
183
|
+
outcome: 'none'
|
|
184
|
+
});
|
|
185
|
+
} else if (status === 'failed' && DELIVERY_FAILURE.test(error)) {
|
|
186
|
+
Object.assign(defaults, {
|
|
187
|
+
acceptance: 'accepted',
|
|
188
|
+
outcome: 'succeeded',
|
|
189
|
+
deliveryState: 'failed'
|
|
190
|
+
});
|
|
191
|
+
} else if (status === 'failed' && WATCH_FAILURE.test(error)) {
|
|
192
|
+
Object.assign(defaults, {
|
|
193
|
+
acceptance: hasRemoteId ? 'accepted' : 'unknown',
|
|
194
|
+
watchState: hasRemoteId ? 'exhausted' : 'idle',
|
|
195
|
+
outcome: 'unknown'
|
|
196
|
+
});
|
|
197
|
+
} else if (status === 'canceled') {
|
|
198
|
+
Object.assign(defaults, {
|
|
199
|
+
acceptance: hasRemoteId ? 'accepted' : 'unknown',
|
|
200
|
+
outcome: 'unknown',
|
|
201
|
+
cancelState: 'unknown'
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const field of V2_FIELDS) {
|
|
206
|
+
if (!Object.prototype.hasOwnProperty.call(out, field)) out[field] = defaults[field];
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|