@unson/brainbase-mcp 0.7.0 → 0.8.0
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/contracts/foundation/graph-history.sql +634 -0
- package/contracts/judgment-value-proof/schema.json +43 -1
- package/dist/canonical-task-service.js +16 -5
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +107 -0
- package/dist/company-os-objectives.d.ts +11 -0
- package/dist/company-os-objectives.js +84 -47
- package/dist/foundation-authenticated-tools.d.ts +56 -0
- package/dist/foundation-authenticated-tools.js +347 -0
- package/dist/foundation-graph-http.d.ts +43 -0
- package/dist/foundation-graph-http.js +243 -0
- package/dist/foundation-graph-write.d.ts +90 -0
- package/dist/foundation-graph-write.js +447 -0
- package/dist/foundation-public-provider.d.ts +140 -0
- package/dist/foundation-public-provider.js +192 -0
- package/dist/graph-corrections.d.ts +157 -0
- package/dist/graph-corrections.js +620 -0
- package/dist/graph-foundation-reader.d.ts +60 -0
- package/dist/graph-foundation-reader.js +494 -0
- package/dist/graph-retrieval.d.ts +6 -1
- package/dist/graph-revision-reader.d.ts +80 -0
- package/dist/graph-revision-reader.js +246 -0
- package/dist/graph-web-http.d.ts +26 -0
- package/dist/graph-web-http.js +215 -0
- package/dist/graph-web.d.ts +364 -0
- package/dist/graph-web.js +639 -0
- package/dist/judgment-problem-snapshot.d.ts +30 -2
- package/dist/judgment-problem-snapshot.js +302 -6
- package/dist/judgment-value-proof-review.d.ts +166 -0
- package/dist/judgment-value-proof-review.js +437 -0
- package/dist/judgment-value-proof.d.ts +28 -1
- package/dist/judgment-value-proof.js +53 -8
- package/dist/knowledge-continuation.d.ts +127 -0
- package/dist/knowledge-continuation.js +446 -0
- package/dist/knowledge-lookup.d.ts +178 -0
- package/dist/knowledge-lookup.js +758 -0
- package/dist/local-web-fetch-bridge.d.ts +5 -0
- package/dist/local-web-fetch-bridge.js +37 -0
- package/dist/local-web-host.d.ts +109 -0
- package/dist/local-web-host.js +688 -0
- package/dist/local-web-security.d.ts +25 -0
- package/dist/local-web-security.js +95 -0
- package/dist/organization-graph.d.ts +15 -0
- package/dist/organization-graph.js +24 -4
- package/dist/personal-knowledge.d.ts +12 -0
- package/dist/personal-knowledge.js +29 -0
- package/dist/personal-memory-handover.d.ts +108 -0
- package/dist/personal-memory-handover.js +369 -0
- package/dist/philosophy-revision-reader.d.ts +85 -0
- package/dist/philosophy-revision-reader.js +241 -0
- package/dist/portable-graph.d.ts +24 -0
- package/dist/portable-graph.js +77 -0
- package/dist/server.d.ts +7 -2
- package/dist/server.js +9 -6
- package/dist/value-proof-review-http.d.ts +25 -0
- package/dist/value-proof-review-http.js +185 -0
- package/dist/world-model.d.ts +21 -3
- package/dist/world-model.js +94 -12
- package/package.json +94 -3
- package/ui/brainbase-tokens.css +56 -0
- package/ui/graph-own-share.js +367 -0
- package/ui/graph-projects-view.css +23 -0
- package/ui/graph-projects-view.js +730 -0
- package/ui/graph-registry-view.css +101 -0
- package/ui/graph-registry-view.js +804 -0
- package/ui/graph-view-shared.css +522 -0
- package/ui/graph-view-shared.js +1227 -0
- package/ui/judgment-view.css +119 -45
- package/ui/local-web-shell.css +396 -0
- package/ui/local-web-shell.js +378 -0
- package/ui/objective-editor-http-port.js +154 -0
- package/ui/objective-editor.css +180 -74
- package/ui/objective-editor.js +490 -43
- package/ui/outcome-knowledge.css +50 -14
- package/ui/outcome-knowledge.js +15 -1
- package/ui/outcome-mana.css +91 -14
- package/ui/outcome-mana.js +958 -61
- package/ui/value-proof-review.css +504 -0
- package/ui/value-proof-review.js +1104 -0
- package/ui/workspace-kit.css +485 -0
- package/ui/workspace-kit.js +229 -0
- package/ui/world-model-view.css +188 -0
- package/ui/world-model-view.js +486 -0
package/ui/outcome-mana.js
CHANGED
|
@@ -17,6 +17,55 @@ export const MANA_STORY_IDS = Object.freeze([
|
|
|
17
17
|
'story-brainbase-outcome-mana-run-control',
|
|
18
18
|
]);
|
|
19
19
|
|
|
20
|
+
// These identifiers are accepted by the Mana contract API. The runtime
|
|
21
|
+
// preflight remains the authority for whether either profile can run.
|
|
22
|
+
// The contract form offers them only when the host passes them (or its own
|
|
23
|
+
// list with the same shape) as `executionProfiles`.
|
|
24
|
+
export const MANA_EXECUTION_PROFILES = Object.freeze([
|
|
25
|
+
Object.freeze({
|
|
26
|
+
id: 'meeting_minutes_github_v1',
|
|
27
|
+
label: '会議録を作成してGitHubへ保存',
|
|
28
|
+
input: 'この委任で選んだ会議資料',
|
|
29
|
+
output: '決定事項・担当・期限をまとめた会議録',
|
|
30
|
+
destination: 'この契約で選ぶGitHubリポジトリ',
|
|
31
|
+
inputConnectorIds: ['github'],
|
|
32
|
+
destinationConnectorId: 'github',
|
|
33
|
+
inputConnectorLabel: 'GitHub',
|
|
34
|
+
destinationConnectorLabel: 'GitHub',
|
|
35
|
+
}),
|
|
36
|
+
Object.freeze({
|
|
37
|
+
id: 'project_report_google_drive_v1',
|
|
38
|
+
label: 'プロジェクト報告を作成してGoogle Driveへ保存',
|
|
39
|
+
input: 'GitHubの確定コミットにあるプロジェクト資料',
|
|
40
|
+
output: '出典・版・取得証跡を保持したプロジェクト報告',
|
|
41
|
+
destination: 'この契約で選ぶGoogle Driveフォルダ',
|
|
42
|
+
inputConnectorIds: ['github'],
|
|
43
|
+
destinationConnectorId: 'drive',
|
|
44
|
+
inputConnectorLabel: 'GitHub',
|
|
45
|
+
destinationConnectorLabel: 'Google Drive',
|
|
46
|
+
}),
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
export function profileInputValidationReasons(profile, inputReferences = [], selectableResources = []) {
|
|
50
|
+
if (!profile) return ['委任する仕事を選んでください。'];
|
|
51
|
+
const references = Array.isArray(inputReferences) ? inputReferences : [];
|
|
52
|
+
const resources = Array.isArray(selectableResources) ? selectableResources : [];
|
|
53
|
+
const usable = references.filter((reference) => resources.some((resource) => resource.id === reference.id
|
|
54
|
+
&& profile.inputConnectorIds.includes(resource.connectorId)));
|
|
55
|
+
const reasons = [];
|
|
56
|
+
if (usable.length === 0) reasons.push(`${profile.inputConnectorLabel}の接続確認済み入力資料を1つ以上選んでください。`);
|
|
57
|
+
if (usable.length !== references.length) reasons.push('選んだ仕事で利用できない入力資料が含まれています。入力資料を選び直してください。');
|
|
58
|
+
if (profile.id === 'project_report_google_drive_v1'
|
|
59
|
+
&& references.some((reference) => !/^[0-9a-f]{40}$/u.test(reference.version ?? ''))) {
|
|
60
|
+
reasons.push('プロジェクト報告のGitHub資料は40文字のコミットSHAで版を固定してください。');
|
|
61
|
+
}
|
|
62
|
+
if (profile.id === 'meeting_minutes_github_v1'
|
|
63
|
+
&& references.some((reference) => reference.id?.startsWith('github:repo:'))) {
|
|
64
|
+
reasons.push('会議録には文字起こしファイルを選んでください。リポジトリ全体の整理には「プロジェクト報告」を選んでください。');
|
|
65
|
+
}
|
|
66
|
+
return reasons;
|
|
67
|
+
}
|
|
68
|
+
|
|
20
69
|
const UNKNOWN = '未確認';
|
|
21
70
|
const EMPTY = 'empty';
|
|
22
71
|
const STOPPED_RUN_RESTART_UNAVAILABLE = '停止した実行は再開できません。';
|
|
@@ -47,6 +96,37 @@ const STATUS_LABELS = Object.freeze({
|
|
|
47
96
|
stale: '再試験が必要',
|
|
48
97
|
permission_denied: '権限不足',
|
|
49
98
|
error_retryable: '再試行可能なエラー',
|
|
99
|
+
available: '利用可能',
|
|
100
|
+
unavailable: '利用不可',
|
|
101
|
+
not_evaluated: '未評価',
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const PREFLIGHT_ACTION_LABELS = Object.freeze({
|
|
105
|
+
review_contract: '成果契約を確認',
|
|
106
|
+
add_input_reference: '入力資料を追加',
|
|
107
|
+
connect_input_provider: '入力元アプリを接続',
|
|
108
|
+
configure_destination: '保存先を設定',
|
|
109
|
+
define_completion_criteria: '完了条件を設定',
|
|
110
|
+
connect_artifact_provider: '保存先アプリを接続',
|
|
111
|
+
verify_artifact_destination: '保存先を確認',
|
|
112
|
+
configure_generator: '生成機能を設定',
|
|
113
|
+
configure_trigger: '起動条件を設定',
|
|
114
|
+
connect_knowledge_provider: '知識基盤を接続',
|
|
115
|
+
configure_authority: '権限を設定',
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const PREFLIGHT_REASON_LABELS = Object.freeze({
|
|
119
|
+
contract_invalid: '成果契約に不足があります',
|
|
120
|
+
input_missing: '入力資料が指定されていません',
|
|
121
|
+
input_adapter_missing: '入力元アプリが接続されていません',
|
|
122
|
+
destination_missing: '保存先が指定されていません',
|
|
123
|
+
completion_criteria_missing: '完了条件が定義されていません',
|
|
124
|
+
artifact_adapter_missing: '保存先アプリが接続されていません',
|
|
125
|
+
artifact_destination_unverified: '保存先を確認できていません',
|
|
126
|
+
generator_missing: '成果を生成する機能が設定されていません',
|
|
127
|
+
trigger_missing: '起動条件が設定されていません',
|
|
128
|
+
knowledge_adapter_missing: '知識基盤が接続されていません',
|
|
129
|
+
authority_missing: '実行権限が設定されていません',
|
|
50
130
|
});
|
|
51
131
|
|
|
52
132
|
const AUTHORITY_LABELS = Object.freeze({
|
|
@@ -101,6 +181,8 @@ const MANA_VIEWS = Object.freeze([
|
|
|
101
181
|
{ id: 'settings', label: 'Mana設定', description: 'runtime既定値と監査' },
|
|
102
182
|
]);
|
|
103
183
|
|
|
184
|
+
// The default connector catalog. A host may replace it with `connectors`
|
|
185
|
+
// (for example to add services or use its own brand assets).
|
|
104
186
|
const CONNECTOR_DEFINITIONS = Object.freeze([
|
|
105
187
|
{ id: 'github', label: 'GitHub', description: 'コード・ドキュメントの参照', icon: '/icons/mana/brand-github.svg' },
|
|
106
188
|
{ id: 'drive', label: 'Google Drive', description: '資料・成果物の保存', icon: '/icons/mana/brand-google-drive.svg' },
|
|
@@ -117,6 +199,17 @@ const CONNECTOR_FOUNDATION_KINDS = Object.freeze({
|
|
|
117
199
|
mana: 'mana',
|
|
118
200
|
});
|
|
119
201
|
|
|
202
|
+
// Neutral wording for a host-managed connection catalog. A host replaces any
|
|
203
|
+
// of these through `connectionCatalog.copy`.
|
|
204
|
+
const CONNECTION_CATALOG_COPY = Object.freeze({
|
|
205
|
+
connectionsTitle: '1. 接続',
|
|
206
|
+
connectionsDescription: '認証済みの外部サービスです。プロジェクトごとに認証し直す必要はありません。',
|
|
207
|
+
resourcesDescription: '接続から選んだリポジトリ、フォルダ、チャンネルと、Brainbase内部基盤です。',
|
|
208
|
+
manageNote: '認証と接続の変更は接続の管理画面で行います。プロジェクトで使う資源は下の資源一覧で確認します。',
|
|
209
|
+
manageAction: '接続を管理',
|
|
210
|
+
noSelectableResources: '選択できる接続済み資源がありません。先に「接続状態」で接続とプロジェクト資源を確認してください。',
|
|
211
|
+
});
|
|
212
|
+
|
|
120
213
|
function hasOwn(value, key) {
|
|
121
214
|
return Boolean(value && typeof value === 'object' && Object.prototype.hasOwnProperty.call(value, key));
|
|
122
215
|
}
|
|
@@ -201,6 +294,20 @@ export function normalizeConnector(value, fallbackId = '') {
|
|
|
201
294
|
};
|
|
202
295
|
}
|
|
203
296
|
|
|
297
|
+
function withConnectorDefinition(value, fallbackId = '', definitions = CONNECTOR_DEFINITIONS) {
|
|
298
|
+
const connector = normalizeConnector(value, fallbackId);
|
|
299
|
+
const definition = definitions.find((item) => item.id === connector.id);
|
|
300
|
+
if (!definition) return connector;
|
|
301
|
+
return {
|
|
302
|
+
...connector,
|
|
303
|
+
label: connector.label || definition.label,
|
|
304
|
+
description: connector.description || definition.description,
|
|
305
|
+
// Brand assets are selected by Brainbase, rather than accepted from an
|
|
306
|
+
// API response. This keeps every connection surface visually consistent.
|
|
307
|
+
icon: definition.icon,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
204
311
|
export function normalizeApprovalTarget(value, fallbackContractVersion = null) {
|
|
205
312
|
const source = objectValue(value);
|
|
206
313
|
const effect = objectValue(source.effect);
|
|
@@ -329,8 +436,9 @@ function contractMatchesExpected(contract, { id, version = null, fields = null,
|
|
|
329
436
|
const expected = normalizeContract(fields);
|
|
330
437
|
const source = objectValue(fields);
|
|
331
438
|
const comparisons = [
|
|
332
|
-
['project', 'project_code', 'projectCode'], ['outcome'], ['scope'],
|
|
439
|
+
['project', 'project_code', 'projectCode'], ['profileId', 'profile_id'], ['outcome'], ['scope'],
|
|
333
440
|
['inputRefs', 'input_refs'], ['knowledgeRefs', 'knowledge_refs'], ['judgmentRefs', 'judgment_refs'],
|
|
441
|
+
['allowedResources', 'allowed_resources'],
|
|
334
442
|
['artifactDestination', 'artifact_destination'], ['completionCriteria', 'completion_criteria'],
|
|
335
443
|
['owner'], ['limits'], ['trigger'],
|
|
336
444
|
];
|
|
@@ -383,6 +491,7 @@ export function normalizeContract(value) {
|
|
|
383
491
|
return Array.isArray(refs) ? refs.map(normalizeReference) : [];
|
|
384
492
|
};
|
|
385
493
|
const criteria = source.completionCriteria ?? source.completion_criteria;
|
|
494
|
+
const allowedResources = source.allowedResources ?? source.allowed_resources;
|
|
386
495
|
return {
|
|
387
496
|
raw: value,
|
|
388
497
|
schemaVersion: stringValue(firstPresent(source.schemaVersion, source.schema_version), OUTCOME_DELEGATION_CONTRACT_VERSION),
|
|
@@ -392,11 +501,13 @@ export function normalizeContract(value) {
|
|
|
392
501
|
project: stringValue(firstPresent(source.project, source.project_code, source.projectCode), ''),
|
|
393
502
|
status: normalizeStatus(source.status),
|
|
394
503
|
nextAction: stringValue(firstPresent(source.nextAction, source.next_action), ''),
|
|
504
|
+
profileId: stringValue(firstPresent(source.profileId, source.profile_id), ''),
|
|
395
505
|
outcome: stringValue(source.outcome, ''),
|
|
396
506
|
scope: stringValue(source.scope, ''),
|
|
397
507
|
inputRefs: references('inputRefs', 'input_refs'),
|
|
398
508
|
knowledgeRefs: references('knowledgeRefs', 'knowledge_refs'),
|
|
399
509
|
judgmentRefs: references('judgmentRefs', 'judgment_refs'),
|
|
510
|
+
allowedResources: Array.isArray(allowedResources) ? allowedResources : [],
|
|
400
511
|
artifactDestination: {
|
|
401
512
|
adapterId: stringValue(firstPresent(destination.adapterId, destination.adapter_id), ''),
|
|
402
513
|
location: stringValue(destination.location, ''),
|
|
@@ -450,9 +561,11 @@ export function normalizeAuthority(value) {
|
|
|
450
561
|
|
|
451
562
|
export function normalizeRun(value) {
|
|
452
563
|
const raw = objectValue(value);
|
|
453
|
-
const source = objectValue(raw.run ?? raw);
|
|
564
|
+
const source = objectValue(raw.run ?? raw.execution ?? raw.testRun ?? raw.test_run ?? raw);
|
|
454
565
|
const completion = objectValue(source.completion ?? source.completion_result ?? source.completionResult);
|
|
455
|
-
const
|
|
566
|
+
const artifact = objectValue(source.artifact);
|
|
567
|
+
const artifacts = source.artifacts ?? source.artifact_refs ?? source.artifactRefs
|
|
568
|
+
?? (artifact.saveReceipt ? [artifact.saveReceipt] : []);
|
|
456
569
|
const evidence = source.evidence ?? source.references ?? [];
|
|
457
570
|
const stages = Array.isArray(source.stages) ? source.stages : [];
|
|
458
571
|
const status = normalizeStatus(source.status ?? source.state);
|
|
@@ -465,19 +578,24 @@ export function normalizeRun(value) {
|
|
|
465
578
|
const trigger = typeof triggerSource === 'string'
|
|
466
579
|
? triggerSource
|
|
467
580
|
: firstPresent(objectValue(triggerSource).type, objectValue(triggerSource).triggerType, objectValue(triggerSource).trigger_type, source.triggerType, source.trigger_type, source.triggerReason, source.trigger_reason, source.reason) ?? null;
|
|
468
|
-
const artifactReadback = source.artifactReadback ?? source.artifact_readback ?? source.readback;
|
|
581
|
+
const artifactReadback = source.artifactReadback ?? source.artifact_readback ?? source.readback ?? artifact.readback;
|
|
469
582
|
const judgment = source.judgment ?? source.judgment_result ?? source.judgmentResult;
|
|
470
583
|
const judgmentReason = firstPresent(
|
|
471
584
|
source.judgmentReason, source.judgment_reason,
|
|
472
585
|
objectValue(judgment).reason, source.reason_detail,
|
|
473
586
|
) ?? null;
|
|
587
|
+
const preflightSource = objectValue(source.preflight ?? raw.preflight);
|
|
588
|
+
const preflightChecks = Array.isArray(preflightSource.checks) ? preflightSource.checks : [];
|
|
474
589
|
return {
|
|
475
590
|
raw: value,
|
|
476
591
|
id: stringValue(firstPresent(source.runId, source.run_id, source.id), ''),
|
|
477
592
|
contractId: stringValue(firstPresent(source.contractId, source.contract_id), ''),
|
|
478
593
|
contractVersion: integerValue(source.contractVersion ?? source.contract_version, null),
|
|
594
|
+
profileId: stringValue(firstPresent(source.profileId, source.profile_id), ''),
|
|
479
595
|
mode: normalizeStatus(source.mode),
|
|
480
596
|
status: RUN_STATUSES.has(status) ? status : status ?? null,
|
|
597
|
+
errorCode: stringValue(firstPresent(source.errorCode, source.error_code), ''),
|
|
598
|
+
errorMessage: stringValue(firstPresent(source.errorMessage, source.error_message), ''),
|
|
481
599
|
safeTest: {
|
|
482
600
|
configSnapshot: safeTestSource.configSnapshot ?? safeTestSource.config_snapshot ?? null,
|
|
483
601
|
configHash: stringValue(firstPresent(safeTestSource.configHash, safeTestSource.config_hash), ''),
|
|
@@ -491,6 +609,19 @@ export function normalizeRun(value) {
|
|
|
491
609
|
artifactReadback: artifactReadback === undefined ? null : artifactReadback,
|
|
492
610
|
judgment: judgment === undefined ? null : judgment,
|
|
493
611
|
judgmentReason,
|
|
612
|
+
preflight: {
|
|
613
|
+
available: typeof preflightSource.available === 'boolean' ? preflightSource.available : null,
|
|
614
|
+
checks: preflightChecks.map((value, index) => {
|
|
615
|
+
const check = objectValue(value);
|
|
616
|
+
return {
|
|
617
|
+
id: stringValue(firstPresent(check.id, check.checkId, check.check_id), `check-${index + 1}`),
|
|
618
|
+
status: normalizeStatus(check.status ?? check.state),
|
|
619
|
+
reason: stringValue(firstPresent(check.reason, check.reason_code), ''),
|
|
620
|
+
action: stringValue(firstPresent(check.action, check.required_action), ''),
|
|
621
|
+
resumeFrom: stringValue(firstPresent(check.resumeFrom, check.resume_from), ''),
|
|
622
|
+
};
|
|
623
|
+
}),
|
|
624
|
+
},
|
|
494
625
|
stage: stringValue(firstPresent(source.stage, source.step, source.phase), ''),
|
|
495
626
|
stages: stages.map((value) => {
|
|
496
627
|
const stage = objectValue(value);
|
|
@@ -509,7 +640,7 @@ export function normalizeRun(value) {
|
|
|
509
640
|
artifacts: Array.isArray(artifacts) ? artifacts : [],
|
|
510
641
|
completion: {
|
|
511
642
|
state: normalizeStatus(firstPresent(completion.state, completion.status, source.completion_state)),
|
|
512
|
-
criteria: Array.isArray(completion.criteria) ? completion.criteria : [],
|
|
643
|
+
criteria: Array.isArray(completion.criteria) ? completion.criteria : Array.isArray(source.criteria) ? source.criteria : [],
|
|
513
644
|
reason: stringValue(firstPresent(completion.reason, completion.error), ''),
|
|
514
645
|
},
|
|
515
646
|
pendingApproval: normalizeApprovalTarget(source.pendingApproval ?? source.pending_approval, integerValue(source.contractVersion ?? source.contract_version, null)),
|
|
@@ -518,6 +649,30 @@ export function normalizeRun(value) {
|
|
|
518
649
|
};
|
|
519
650
|
}
|
|
520
651
|
|
|
652
|
+
/**
|
|
653
|
+
* Readiness is only known from a preflight bound to this exact saved profile version.
|
|
654
|
+
* `profiles` is the list offered by the host (defaults to MANA_EXECUTION_PROFILES).
|
|
655
|
+
*/
|
|
656
|
+
export function executionProfileReadiness(profileId, contractValue, runValues = [], profiles = MANA_EXECUTION_PROFILES) {
|
|
657
|
+
const profile = (Array.isArray(profiles) ? profiles : []).find((item) => item.id === profileId);
|
|
658
|
+
const contract = normalizeContract(contractValue);
|
|
659
|
+
if (!profile || !contract.contractId || !Number.isInteger(contract.version) || contract.profileId !== profileId) {
|
|
660
|
+
return { status: 'unknown', label: '事前確認: 未確認', checks: [] };
|
|
661
|
+
}
|
|
662
|
+
for (const value of runValues) {
|
|
663
|
+
if (!value) continue;
|
|
664
|
+
const run = normalizeRun(value);
|
|
665
|
+
if (run.contractId !== contract.contractId || run.contractVersion !== contract.version || run.profileId !== profileId) continue;
|
|
666
|
+
if (typeof run.preflight.available !== 'boolean') continue;
|
|
667
|
+
return {
|
|
668
|
+
status: run.preflight.available ? 'available' : 'unavailable',
|
|
669
|
+
label: run.preflight.available ? '事前確認: 実行可能' : '事前確認: 実行不可',
|
|
670
|
+
checks: run.preflight.checks,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
return { status: 'unknown', label: '事前確認: 未確認', checks: [] };
|
|
674
|
+
}
|
|
675
|
+
|
|
521
676
|
function safeTestStatus(value, contract) {
|
|
522
677
|
const run = normalizeRun(value);
|
|
523
678
|
const current = objectValue(contract);
|
|
@@ -584,12 +739,16 @@ function parseCriteriaLines(value) {
|
|
|
584
739
|
|
|
585
740
|
function contractDraftFromForm(fields) {
|
|
586
741
|
const criterionText = fields.completionCriteria.value;
|
|
742
|
+
// The profile and resource pickers exist only when the host offers
|
|
743
|
+
// execution profiles. The free-text form keeps its original request body.
|
|
587
744
|
const body = {
|
|
745
|
+
...(fields.profileId ? { profileId: fields.profileId.value } : {}),
|
|
588
746
|
outcome: fields.outcome.value,
|
|
589
747
|
scope: fields.scope.value,
|
|
590
748
|
inputRefs: parseReferenceLines(fields.inputRefs.value),
|
|
591
749
|
knowledgeRefs: parseReferenceLines(fields.knowledgeRefs.value),
|
|
592
750
|
judgmentRefs: parseReferenceLines(fields.judgmentRefs.value),
|
|
751
|
+
...(fields.allowedResources ? { allowedResources: stringValue(fields.allowedResources.value).split(/\r?\n/).map((line) => line.trim()).filter(Boolean) } : {}),
|
|
593
752
|
artifactDestination: {
|
|
594
753
|
adapterId: fields.adapterId.value,
|
|
595
754
|
location: fields.location.value,
|
|
@@ -699,7 +858,7 @@ function stateMessage(document, status, options = {}) {
|
|
|
699
858
|
const item = createElement(document, 'div', { className: `outcome-mana-state outcome-mana-error outcome-mana-error-${state}`, attrs: { role: 'alert' } });
|
|
700
859
|
item.append(createElement(document, 'strong', { text: state === 'unknown' ? '状態を確認できません' : state === 'conflict' ? '別の更新を確認しました' : '読み込みを確認できません' }));
|
|
701
860
|
if (options.message) item.append(createElement(document, 'p', { text: options.message }));
|
|
702
|
-
if (options.retry) item.append(button(document, '再試行', options.retry, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
861
|
+
if (options.retry) item.append(button(document, options.retryLabel ?? '再試行', options.retry, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
703
862
|
return item;
|
|
704
863
|
}
|
|
705
864
|
return null;
|
|
@@ -753,6 +912,21 @@ function evidenceList(document, title, values) {
|
|
|
753
912
|
return section;
|
|
754
913
|
}
|
|
755
914
|
|
|
915
|
+
function criterionSummaries(criteria) {
|
|
916
|
+
return (Array.isArray(criteria) ? criteria : []).map((value) => {
|
|
917
|
+
const criterion = objectValue(value);
|
|
918
|
+
return [stringValue(criterion.id, UNKNOWN), statusLabel(criterion.status), stringValue(criterion.reason, '')]
|
|
919
|
+
.filter(Boolean).join(' / ');
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
function artifactReadbackSummary(value) {
|
|
924
|
+
const readback = objectValue(value);
|
|
925
|
+
if (!Object.keys(readback).length) return UNKNOWN;
|
|
926
|
+
return readback.verified === true ? '検証済み'
|
|
927
|
+
: readback.verified === false ? '検証失敗' : '検証状態は未確認';
|
|
928
|
+
}
|
|
929
|
+
|
|
756
930
|
function stageList(document, stages) {
|
|
757
931
|
const section = createElement(document, 'div', { className: 'outcome-mana-evidence' });
|
|
758
932
|
section.append(createElement(document, 'h4', { text: '処理段階' }));
|
|
@@ -827,11 +1001,13 @@ function canManage(session) {
|
|
|
827
1001
|
function contractFormValues(contract, draft) {
|
|
828
1002
|
const source = draft ?? contract ?? normalizeContract({});
|
|
829
1003
|
return {
|
|
1004
|
+
profileId: source.profileId ?? '',
|
|
830
1005
|
outcome: source.outcome ?? '',
|
|
831
1006
|
scope: source.scope ?? '',
|
|
832
1007
|
inputRefs: (source.inputRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
833
1008
|
knowledgeRefs: (source.knowledgeRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
834
1009
|
judgmentRefs: (source.judgmentRefs ?? []).map((item) => `${item.id}${item.version ? `@${item.version}` : ''}`).join('\n'),
|
|
1010
|
+
allowedResources: (source.allowedResources ?? []).map((item) => typeof item === 'string' ? item : firstPresent(item.id, item.resourceId, item.resource_id)).filter(Boolean).join('\n'),
|
|
835
1011
|
adapterId: source.artifactDestination?.adapterId ?? '',
|
|
836
1012
|
location: source.artifactDestination?.location ?? '',
|
|
837
1013
|
environment: source.artifactDestination?.environment ?? 'isolated',
|
|
@@ -845,11 +1021,13 @@ function contractFormValues(contract, draft) {
|
|
|
845
1021
|
|
|
846
1022
|
function contractInput(contract, overrides = {}) {
|
|
847
1023
|
return {
|
|
1024
|
+
profileId: contract.profileId ?? '',
|
|
848
1025
|
outcome: contract.outcome,
|
|
849
1026
|
scope: contract.scope,
|
|
850
1027
|
inputRefs: contract.inputRefs,
|
|
851
1028
|
knowledgeRefs: contract.knowledgeRefs,
|
|
852
1029
|
judgmentRefs: contract.judgmentRefs,
|
|
1030
|
+
allowedResources: contract.allowedResources ?? [],
|
|
853
1031
|
artifactDestination: contract.artifactDestination,
|
|
854
1032
|
completionCriteria: contract.completionCriteria,
|
|
855
1033
|
owner: contract.owner,
|
|
@@ -867,6 +1045,74 @@ function triggerFromContract(contract, draft) {
|
|
|
867
1045
|
return normalizeTrigger(draft?.trigger ?? contract?.trigger);
|
|
868
1046
|
}
|
|
869
1047
|
|
|
1048
|
+
/**
|
|
1049
|
+
* @typedef {object} ManaConnectorDefinition
|
|
1050
|
+
* @property {string} id Stable connector id used by contracts and profiles.
|
|
1051
|
+
* @property {string} label Display name.
|
|
1052
|
+
* @property {string} description One-line purpose.
|
|
1053
|
+
* @property {string} icon Icon URL. The UI always takes the icon from here,
|
|
1054
|
+
* never from an API response.
|
|
1055
|
+
*/
|
|
1056
|
+
|
|
1057
|
+
/**
|
|
1058
|
+
* @typedef {object} ManaConnectionStatus
|
|
1059
|
+
* @property {string} id Connector id.
|
|
1060
|
+
* @property {string} [status] e.g. `connected`, `unconfirmed`, `failed`,
|
|
1061
|
+
* `permission_denied`, `authorization_required`. Missing means `unconfirmed`.
|
|
1062
|
+
* @property {string|null} [account] Account that authenticated the connection.
|
|
1063
|
+
* @property {string|null} [checkedAt] Time the host last verified it.
|
|
1064
|
+
* @property {string|null} [reason] Reason code shown when not connected.
|
|
1065
|
+
*/
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* @typedef {object} ManaConnectionCatalog
|
|
1069
|
+
* @property {string[]} ids Connector ids whose authentication the host
|
|
1070
|
+
* manages. They are listed in `connectors` order. Project resources and
|
|
1071
|
+
* Brainbase/Mana foundations stay in the separate project-resource list.
|
|
1072
|
+
* @property {(context: { project: object, session: object }) => Promise<ManaConnectionStatus[]|{ connections: ManaConnectionStatus[] }>} load
|
|
1073
|
+
* Reads the host's current connection status. A rejection keeps every
|
|
1074
|
+
* connection unconfirmed with the error code as its reason.
|
|
1075
|
+
* @property {Record<string, string>} [labels] Display name per connector id
|
|
1076
|
+
* in the connection list (e.g. the installed app name).
|
|
1077
|
+
* @property {(connectorId: string) => string} [sourceLabel] Where the
|
|
1078
|
+
* project resources of a connector come from. Defaults to the connector label.
|
|
1079
|
+
* @property {Record<string, string>} [reasonLabels] Labels for host-specific
|
|
1080
|
+
* reason codes. They take precedence over the built-in labels.
|
|
1081
|
+
* @property {Partial<typeof CONNECTION_CATALOG_COPY>} [copy] Replaces the
|
|
1082
|
+
* neutral wording of the connection view and the resource picker.
|
|
1083
|
+
*/
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Creates the Mana outcome-delegation workspace.
|
|
1087
|
+
*
|
|
1088
|
+
* Besides `document`, `root`, `api`, `apiMutation`, `project`, `session`,
|
|
1089
|
+
* `paths`, `pathFor`, `basePath`, `confirm`, `onChange`, and `autoLoad`, the
|
|
1090
|
+
* host may pass these options. Without them the workspace behaves as the
|
|
1091
|
+
* single-owner default: the built-in connector list, foundation checks
|
|
1092
|
+
* through `paths.foundationCheck`, and a free-text contract form.
|
|
1093
|
+
*
|
|
1094
|
+
* @param {object} options
|
|
1095
|
+
* @param {ManaConnectorDefinition[]} [options.connectors] Replaces the
|
|
1096
|
+
* default connector list (GitHub, Google Drive, Slack, Brainbase Graph,
|
|
1097
|
+
* Mana runtime) and its order.
|
|
1098
|
+
* @param {ManaConnectionCatalog} [options.connectionCatalog] Reads connection
|
|
1099
|
+
* status from the host instead of foundation checks, and shows connections,
|
|
1100
|
+
* project resources, and the delegation's allowed range as three sections.
|
|
1101
|
+
* @param {(connectorId: string) => void} [options.onManageConnection] Opens
|
|
1102
|
+
* the host's connection management for the selected connector. The button
|
|
1103
|
+
* appears only with `connectionCatalog`.
|
|
1104
|
+
* @param {Array<typeof MANA_EXECUTION_PROFILES[number]>} [options.executionProfiles]
|
|
1105
|
+
* Execution profiles offered by the contract form. When non-empty, the form
|
|
1106
|
+
* asks for a profile and picks input references, allowed resources, and the
|
|
1107
|
+
* artifact destination from loaded connection resources, and it loads
|
|
1108
|
+
* connections before a delegation is edited. Pass MANA_EXECUTION_PROFILES
|
|
1109
|
+
* for the Mana runtime profiles.
|
|
1110
|
+
* @param {Storage|null} [options.sessionStorage] Keeps the accepted safe-test
|
|
1111
|
+
* run ID per actor, project, and contract. Defaults to the browser's
|
|
1112
|
+
* sessionStorage; `null` disables it.
|
|
1113
|
+
* @param {typeof setTimeout} [options.setTimeout] Scheduler for safe-test readback.
|
|
1114
|
+
* @param {typeof clearTimeout} [options.clearTimeout] Cancels a scheduled readback.
|
|
1115
|
+
*/
|
|
870
1116
|
export function createManaOutcomeUI(options = {}) {
|
|
871
1117
|
const document = options.document ?? globalThis.document;
|
|
872
1118
|
if (!document) throw new Error('outcome_mana_document_unavailable');
|
|
@@ -877,6 +1123,16 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
877
1123
|
const pathBuilder = typeof options.pathFor === 'function' ? options.pathFor : null;
|
|
878
1124
|
const projectInput = options.project;
|
|
879
1125
|
const sessionInput = options.session;
|
|
1126
|
+
const connectorDefinitions = Array.isArray(options.connectors) && options.connectors.length
|
|
1127
|
+
? options.connectors
|
|
1128
|
+
: CONNECTOR_DEFINITIONS;
|
|
1129
|
+
const connectionCatalog = options.connectionCatalog && typeof options.connectionCatalog.load === 'function'
|
|
1130
|
+
? options.connectionCatalog
|
|
1131
|
+
: null;
|
|
1132
|
+
const catalogIds = Array.isArray(connectionCatalog?.ids) ? connectionCatalog.ids : [];
|
|
1133
|
+
const catalogCopy = { ...CONNECTION_CATALOG_COPY, ...objectValue(connectionCatalog?.copy) };
|
|
1134
|
+
const executionProfiles = Array.isArray(options.executionProfiles) ? options.executionProfiles : [];
|
|
1135
|
+
const profilePicker = executionProfiles.length > 0;
|
|
880
1136
|
const state = {
|
|
881
1137
|
// null means no user choice has been made yet. render() derives a useful
|
|
882
1138
|
// first step from the state so existing deep links/readbacks land on the
|
|
@@ -887,13 +1143,56 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
887
1143
|
contract: { status: 'loading', records: [], selectedId: null, detail: null, error: null, formDraft: null, pendingSave: null },
|
|
888
1144
|
authority: { status: 'idle', matrix: [], raw: null, error: null, draft: null },
|
|
889
1145
|
triggers: { status: 'idle', trigger: null, error: null, draft: null, catalogStatus: 'idle', schedules: [], subscriptions: [], catalogError: null },
|
|
890
|
-
safeTest: { status: 'idle', result: null, error: null, formDraft: null },
|
|
1146
|
+
safeTest: { status: 'idle', result: null, runId: null, contractId: null, error: null, formDraft: null },
|
|
891
1147
|
activation: { status: 'idle', error: null, message: '' },
|
|
892
1148
|
runs: { status: 'idle', records: [], selectedId: null, detail: null, error: null },
|
|
893
1149
|
runAction: { status: 'idle', error: null, draft: null },
|
|
894
1150
|
connections: { status: 'unknown', records: [], selectedId: null, detail: null, error: null },
|
|
895
1151
|
manaSettings: { status: 'unknown', data: null, error: null, draft: null, saveStatus: 'idle', saveError: null },
|
|
896
1152
|
};
|
|
1153
|
+
let contractDetailRequestGeneration = 0;
|
|
1154
|
+
let authorityRequestGeneration = 0;
|
|
1155
|
+
let safeTestTimer = null;
|
|
1156
|
+
let safeTestReadCount = 0;
|
|
1157
|
+
const safeTestReadLimit = 450;
|
|
1158
|
+
const scheduleTimeout = options.setTimeout ?? globalThis.setTimeout;
|
|
1159
|
+
const cancelTimeout = options.clearTimeout ?? globalThis.clearTimeout;
|
|
1160
|
+
let tabStorage = options.sessionStorage;
|
|
1161
|
+
if (tabStorage === undefined) {
|
|
1162
|
+
try { tabStorage = globalThis.sessionStorage; } catch { /* storage is optional */ }
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
function safeTestStorageKey(id) {
|
|
1166
|
+
const projectCode = currentProject().code;
|
|
1167
|
+
const actorId = currentSession().actorId;
|
|
1168
|
+
return projectCode && actorId && id ? `mana-safe-test-run:${actorId}:${projectCode}:${id}` : null;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function rememberSafeTestRun(id, runId) {
|
|
1172
|
+
const key = safeTestStorageKey(id);
|
|
1173
|
+
if (!key || !runId) return;
|
|
1174
|
+
try { tabStorage?.setItem(key, runId); } catch { /* storage is optional */ }
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
function stopSafeTestReadback() {
|
|
1178
|
+
if (safeTestTimer !== null) cancelTimeout(safeTestTimer);
|
|
1179
|
+
safeTestTimer = null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
function scheduleSafeTestReadback(runId) {
|
|
1183
|
+
stopSafeTestReadback();
|
|
1184
|
+
if (safeTestReadCount >= safeTestReadLimit) {
|
|
1185
|
+
state.safeTest.status = 'unknown';
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
safeTestTimer = scheduleTimeout(async () => {
|
|
1189
|
+
safeTestTimer = null;
|
|
1190
|
+
if (state.safeTest.runId !== runId) return;
|
|
1191
|
+
safeTestReadCount += 1;
|
|
1192
|
+
await refreshSafeTestReadback();
|
|
1193
|
+
}, 2000);
|
|
1194
|
+
safeTestTimer?.unref?.();
|
|
1195
|
+
}
|
|
897
1196
|
|
|
898
1197
|
const viewIds = new Set([...MANA_VIEWS.map((view) => view.id), 'delegation_settings']);
|
|
899
1198
|
|
|
@@ -956,9 +1255,12 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
956
1255
|
function currentProject() { return projectValue(projectInput); }
|
|
957
1256
|
function currentSession() { return sessionValue(sessionInput); }
|
|
958
1257
|
function currentContract() {
|
|
959
|
-
|
|
1258
|
+
const selectedId = state.contract.selectedId;
|
|
1259
|
+
if (selectedId && state.contract.detail?.contractId === selectedId) return state.contract.detail;
|
|
1260
|
+
if (selectedId) return state.contract.records.find((record) => record.contractId === selectedId) ?? null;
|
|
1261
|
+
return state.contract.detail ?? null;
|
|
960
1262
|
}
|
|
961
|
-
function contractId() { return currentContract()?.contractId || null; }
|
|
1263
|
+
function contractId() { return state.contract.selectedId || currentContract()?.contractId || null; }
|
|
962
1264
|
function currentPaths() {
|
|
963
1265
|
const generated = defaultPathMap(options.basePath ?? '/api/outcome-delegations', currentProject().code);
|
|
964
1266
|
return { ...generated, ...paths };
|
|
@@ -972,6 +1274,10 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
972
1274
|
function connectorSource(project, id) {
|
|
973
1275
|
const foundations = objectValue(project.foundation_values);
|
|
974
1276
|
const resources = objectValue(project.resources);
|
|
1277
|
+
// Drive has a primary project folder and an optional artifact destination.
|
|
1278
|
+
// The resource collection is authoritative for contract choices, while
|
|
1279
|
+
// foundation_values.drive remains the single primary-folder edit value.
|
|
1280
|
+
if (id === 'drive' && hasOwn(resources, 'drive')) return resources.drive;
|
|
975
1281
|
const aliases = {
|
|
976
1282
|
brainbase: ['brainbase', 'knowledge'],
|
|
977
1283
|
drive: ['drive', 'google_drive', 'googleDrive'],
|
|
@@ -987,15 +1293,64 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
987
1293
|
return null;
|
|
988
1294
|
}
|
|
989
1295
|
|
|
1296
|
+
function projectResourceId(connectorId, value) {
|
|
1297
|
+
const source = objectValue(value);
|
|
1298
|
+
const explicit = firstPresent(source.id, source.resourceId, source.resource_id);
|
|
1299
|
+
if (explicit) return stringValue(explicit).trim();
|
|
1300
|
+
const identifier = firstPresent(
|
|
1301
|
+
source.identifier,
|
|
1302
|
+
source.folder_id,
|
|
1303
|
+
source.folderId,
|
|
1304
|
+
source.channel_id,
|
|
1305
|
+
source.channelId,
|
|
1306
|
+
typeof value === 'string' || typeof value === 'number' ? value : null,
|
|
1307
|
+
);
|
|
1308
|
+
if (!identifier) return '';
|
|
1309
|
+
const identifierText = stringValue(identifier).trim();
|
|
1310
|
+
if (identifierText.startsWith(`${connectorId}:`)) return identifierText;
|
|
1311
|
+
const prefixes = {
|
|
1312
|
+
github: 'github:repo:',
|
|
1313
|
+
drive: 'drive:folder:',
|
|
1314
|
+
slack: 'slack:channel:',
|
|
1315
|
+
brainbase: 'brainbase:knowledge:',
|
|
1316
|
+
mana: 'mana:runtime:',
|
|
1317
|
+
};
|
|
1318
|
+
return `${prefixes[connectorId] ?? `${connectorId}:resource:`}${identifierText}`;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
function projectResources(connectorId, source) {
|
|
1322
|
+
if (source === null || source === undefined) return [];
|
|
1323
|
+
const record = objectValue(source);
|
|
1324
|
+
const values = Array.isArray(source)
|
|
1325
|
+
? source
|
|
1326
|
+
: Array.isArray(record.items) ? record.items : [source];
|
|
1327
|
+
return values.map((value) => {
|
|
1328
|
+
const resource = objectValue(value);
|
|
1329
|
+
const id = projectResourceId(connectorId, value);
|
|
1330
|
+
if (!id) return null;
|
|
1331
|
+
return {
|
|
1332
|
+
...resource,
|
|
1333
|
+
id,
|
|
1334
|
+
label: firstPresent(
|
|
1335
|
+
resource.label,
|
|
1336
|
+
resource.name,
|
|
1337
|
+
resource.title,
|
|
1338
|
+
resource.identifier,
|
|
1339
|
+
typeof value === 'string' || typeof value === 'number' ? value : null,
|
|
1340
|
+
id,
|
|
1341
|
+
),
|
|
1342
|
+
};
|
|
1343
|
+
}).filter(Boolean);
|
|
1344
|
+
}
|
|
1345
|
+
|
|
990
1346
|
function connectorRecordsFromProject() {
|
|
991
1347
|
const project = currentProject();
|
|
992
|
-
return
|
|
1348
|
+
return connectorDefinitions.map((definition) => {
|
|
993
1349
|
const source = connectorSource(project, definition.id);
|
|
994
|
-
//
|
|
995
|
-
//
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
: (source !== null && source !== undefined && typeof source !== 'object' ? { account: source } : source);
|
|
1350
|
+
// Project registration and connection authentication are distinct.
|
|
1351
|
+
// Normalize the former into stable contract resource IDs without
|
|
1352
|
+
// inventing a connector status or account from the project payload.
|
|
1353
|
+
const value = { resources: projectResources(definition.id, source) };
|
|
999
1354
|
const normalized = normalizeConnector(value, definition.id);
|
|
1000
1355
|
return {
|
|
1001
1356
|
...normalized,
|
|
@@ -1009,16 +1364,26 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1009
1364
|
function connectionStatusLabel(status) {
|
|
1010
1365
|
const normalized = normalizeStatus(status);
|
|
1011
1366
|
if (['connected', 'verified', 'readback_verified'].includes(normalized)) return '接続確認済み';
|
|
1012
|
-
if (['active', 'enabled', 'ready', 'available'].includes(normalized)) return '
|
|
1367
|
+
if (['active', 'enabled', 'ready', 'available'].includes(normalized)) return '登録済み(接続未確認)';
|
|
1013
1368
|
if (['failed', 'error', 'unavailable'].includes(normalized)) return '確認失敗';
|
|
1014
1369
|
if (['unregistered', 'not_registered'].includes(normalized)) return '未登録';
|
|
1015
1370
|
if (['unconfigured', 'misconfigured'].includes(normalized)) return '確認設定なし';
|
|
1016
1371
|
if (['permission_denied', 'forbidden', 'unauthorized'].includes(normalized)) return '権限不足';
|
|
1372
|
+
if (['authorization_required', 'required_scope_missing'].includes(normalized)) return '追加権限が必要';
|
|
1017
1373
|
if (['timeout', 'timed_out'].includes(normalized)) return 'タイムアウト';
|
|
1018
1374
|
if (['rejected', 'unconfirmed', 'readback_mismatch'].includes(normalized)) return '確認できません';
|
|
1019
1375
|
return UNKNOWN;
|
|
1020
1376
|
}
|
|
1021
1377
|
|
|
1378
|
+
function projectResourceStatusLabel(connector) {
|
|
1379
|
+
return connector.resources?.length || connector.account ? '登録済み' : '未登録';
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function connectionSourceLabel(id) {
|
|
1383
|
+
const hosted = typeof connectionCatalog?.sourceLabel === 'function' ? connectionCatalog.sourceLabel(id) : null;
|
|
1384
|
+
return firstPresent(hosted, connectorDefinitions.find((item) => item.id === id)?.label, id);
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1022
1387
|
function connectionReasonLabel(reason) {
|
|
1023
1388
|
const value = stringValue(reason).trim();
|
|
1024
1389
|
const normalized = normalizeStatus(value);
|
|
@@ -1040,7 +1405,12 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1040
1405
|
provider_rejected: '接続先が確認要求を拒否しました',
|
|
1041
1406
|
provider_readback_mismatch: '登録内容と確認結果が一致しません',
|
|
1042
1407
|
local_preview_only: 'ローカルプレビューでは実接続を確認できません',
|
|
1408
|
+
not_implemented: 'このアプリの接続は準備中です',
|
|
1043
1409
|
};
|
|
1410
|
+
const hosted = objectValue(connectionCatalog?.reasonLabels);
|
|
1411
|
+
if (hasOwn(hosted, value)) return stringValue(hosted[value]);
|
|
1412
|
+
if (normalized && hasOwn(hosted, normalized)) return stringValue(hosted[normalized]);
|
|
1413
|
+
if (/^upstream_http_\d+$/u.test(normalized)) return `接続状態APIが応答しました(HTTP ${normalized.slice(-3)})`;
|
|
1044
1414
|
return labels[normalized] ?? (value || UNKNOWN);
|
|
1045
1415
|
}
|
|
1046
1416
|
|
|
@@ -1061,7 +1431,7 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1061
1431
|
function normalizeConnectorPayload(payload) {
|
|
1062
1432
|
const records = collectionFrom(payload, ['connectors', 'connections', 'items', 'records']);
|
|
1063
1433
|
if (!records) return { state: 'unknown', records: [], raw: payload };
|
|
1064
|
-
return { state: records.length ? 'ready' : 'empty', records: records.map((item) =>
|
|
1434
|
+
return { state: records.length ? 'ready' : 'empty', records: records.map((item) => withConnectorDefinition(item, '', connectorDefinitions)), raw: payload };
|
|
1065
1435
|
}
|
|
1066
1436
|
|
|
1067
1437
|
function connectorFromFoundationCheck(definition, payload, error, attemptedAt) {
|
|
@@ -1084,10 +1454,10 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1084
1454
|
}, definition.id);
|
|
1085
1455
|
}
|
|
1086
1456
|
|
|
1087
|
-
async function
|
|
1457
|
+
async function checkFoundationConnections() {
|
|
1088
1458
|
const attemptedAt = new Date().toISOString();
|
|
1089
|
-
return Promise.all(
|
|
1090
|
-
const kind = CONNECTOR_FOUNDATION_KINDS[definition.id];
|
|
1459
|
+
return Promise.all(connectorDefinitions.map(async (definition) => {
|
|
1460
|
+
const kind = CONNECTOR_FOUNDATION_KINDS[definition.id] ?? definition.id;
|
|
1091
1461
|
const target = path('foundationCheck', { kind });
|
|
1092
1462
|
if (!target || typeof apiMutation !== 'function') return connectorFromFoundationCheck(definition, {}, null, null);
|
|
1093
1463
|
try {
|
|
@@ -1103,13 +1473,60 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1103
1473
|
}));
|
|
1104
1474
|
}
|
|
1105
1475
|
|
|
1476
|
+
function catalogDefinitions() {
|
|
1477
|
+
return connectorDefinitions.filter((item) => catalogIds.includes(item.id));
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function catalogLabel(definition) {
|
|
1481
|
+
return firstPresent(objectValue(connectionCatalog?.labels)[definition.id], definition.label);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// Host-managed authentication and project registration stay separate:
|
|
1485
|
+
// the host reports only connection status, and the resources come from the
|
|
1486
|
+
// project payload.
|
|
1487
|
+
async function checkCatalogConnections() {
|
|
1488
|
+
const payload = await connectionCatalog.load({ project: currentProject(), session: currentSession() });
|
|
1489
|
+
const loaded = collectionFrom(payload, ['connections', 'connectors', 'items', 'records']) ?? [];
|
|
1490
|
+
const byId = new Map(loaded.map((item) => normalizeConnector(item)).map((item) => [item.id, item]));
|
|
1491
|
+
const projectRecords = new Map(connectorRecordsFromProject().map((item) => [item.id, item]));
|
|
1492
|
+
return catalogDefinitions().map((definition) => {
|
|
1493
|
+
const hosted = byId.get(definition.id);
|
|
1494
|
+
return withConnectorDefinition({
|
|
1495
|
+
id: definition.id,
|
|
1496
|
+
label: catalogLabel(definition),
|
|
1497
|
+
description: definition.description,
|
|
1498
|
+
status: hosted?.status ?? 'unconfirmed',
|
|
1499
|
+
account: hosted?.account ?? null,
|
|
1500
|
+
resources: projectRecords.get(definition.id)?.resources ?? [],
|
|
1501
|
+
checkedAt: hosted?.checkedAt ?? null,
|
|
1502
|
+
reason: hosted?.reason ?? null,
|
|
1503
|
+
}, definition.id, connectorDefinitions);
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function catalogPlaceholders(error = null) {
|
|
1508
|
+
const normalizedError = error ? normalizeError(error) : null;
|
|
1509
|
+
return catalogDefinitions().map((definition) => withConnectorDefinition({
|
|
1510
|
+
id: definition.id,
|
|
1511
|
+
label: catalogLabel(definition),
|
|
1512
|
+
description: definition.description,
|
|
1513
|
+
status: error ? (normalizedError.status === 403 ? 'permission_denied' : 'failed') : 'unconfirmed',
|
|
1514
|
+
account: null,
|
|
1515
|
+
checkedAt: null,
|
|
1516
|
+
reason: normalizedError?.code ?? null,
|
|
1517
|
+
}, definition.id, connectorDefinitions));
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1106
1520
|
async function loadConnections() {
|
|
1107
1521
|
const checkTarget = path('foundationCheck', { kind: 'github' });
|
|
1108
1522
|
const target = path('connectors') ?? path('connections');
|
|
1109
1523
|
state.connections.status = 'loading'; state.connections.error = null; render();
|
|
1110
1524
|
try {
|
|
1111
|
-
if (
|
|
1112
|
-
const records = await
|
|
1525
|
+
if (connectionCatalog) {
|
|
1526
|
+
const records = await checkCatalogConnections();
|
|
1527
|
+
state.connections = { ...state.connections, status: 'ready', records, error: null };
|
|
1528
|
+
} else if (checkTarget && typeof apiMutation === 'function') {
|
|
1529
|
+
const records = await checkFoundationConnections();
|
|
1113
1530
|
state.connections = { ...state.connections, status: 'ready', records, error: null };
|
|
1114
1531
|
} else if (!target) {
|
|
1115
1532
|
const records = projectConnectors();
|
|
@@ -1120,10 +1537,30 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1120
1537
|
}
|
|
1121
1538
|
if (!state.connections.selectedId && state.connections.records.length) state.connections.selectedId = state.connections.records[0].id;
|
|
1122
1539
|
state.connections.detail = state.connections.records.find((item) => item.id === state.connections.selectedId) ?? null;
|
|
1123
|
-
} catch (error) {
|
|
1540
|
+
} catch (error) {
|
|
1541
|
+
if (connectionCatalog) {
|
|
1542
|
+
// Keep every host connection visible as unconfirmed rather than
|
|
1543
|
+
// hiding the list or inventing a verification time.
|
|
1544
|
+
state.connections = {
|
|
1545
|
+
...state.connections,
|
|
1546
|
+
status: 'error',
|
|
1547
|
+
records: catalogPlaceholders(error),
|
|
1548
|
+
error: normalizeError(error),
|
|
1549
|
+
};
|
|
1550
|
+
if (!state.connections.selectedId) state.connections.selectedId = state.connections.records[0]?.id ?? null;
|
|
1551
|
+
state.connections.detail = state.connections.records.find((item) => item.id === state.connections.selectedId) ?? null;
|
|
1552
|
+
} else setError(state.connections, error);
|
|
1553
|
+
}
|
|
1124
1554
|
render(); notifyChange(); return state.connections;
|
|
1125
1555
|
}
|
|
1126
1556
|
|
|
1557
|
+
// The profile pickers choose from loaded connection resources, so load
|
|
1558
|
+
// them once before a delegation is edited. The free-text form needs none.
|
|
1559
|
+
async function ensureConnectionsForDelegation() {
|
|
1560
|
+
if (!profilePicker || state.connections.status !== 'unknown') return state.connections;
|
|
1561
|
+
return loadConnections();
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1127
1564
|
async function loadManaSettings() {
|
|
1128
1565
|
const target = path('settings') ?? path('manaSettings');
|
|
1129
1566
|
const mana = objectValue(currentProject().mana);
|
|
@@ -1270,21 +1707,47 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1270
1707
|
|
|
1271
1708
|
async function loadContractDetail(id = contractId(), { renderAfter = true } = {}) {
|
|
1272
1709
|
if (!id) return null;
|
|
1710
|
+
const requestedId = id;
|
|
1711
|
+
const requestGeneration = ++contractDetailRequestGeneration;
|
|
1712
|
+
// A contract refresh changes the version-bound authority context even when
|
|
1713
|
+
// the user returns to the same contract before an older request settles.
|
|
1714
|
+
authorityRequestGeneration += 1;
|
|
1715
|
+
// Authority is version-bound. A detail refresh may return a newer version
|
|
1716
|
+
// for the same contract id, so never carry the previous version's matrix
|
|
1717
|
+
// across that boundary. An embedded authority payload below can replace
|
|
1718
|
+
// this idle state; otherwise the authority stage reloads it explicitly.
|
|
1719
|
+
state.authority = { status: 'idle', matrix: [], error: null, draft: null };
|
|
1720
|
+
state.contract.selectedId = requestedId;
|
|
1273
1721
|
state.contract.status = 'loading'; state.contract.error = null;
|
|
1274
1722
|
if (renderAfter) render();
|
|
1275
1723
|
try {
|
|
1276
|
-
const payload = await callApi(path('contractDetail', { contractId:
|
|
1724
|
+
const payload = await callApi(path('contractDetail', { contractId: requestedId }));
|
|
1725
|
+
if (state.contract.selectedId !== requestedId || contractDetailRequestGeneration !== requestGeneration) return null;
|
|
1277
1726
|
const contract = normalizeContract(payload);
|
|
1278
|
-
state.contract.detail = contract.contractId ? contract : { ...contract, contractId:
|
|
1279
|
-
state.contract.selectedId = id;
|
|
1727
|
+
state.contract.detail = contract.contractId ? contract : { ...contract, contractId: requestedId };
|
|
1280
1728
|
state.contract.status = 'ready';
|
|
1729
|
+
if (state.safeTest.contractId && state.safeTest.contractId !== requestedId) {
|
|
1730
|
+
stopSafeTestReadback();
|
|
1731
|
+
state.safeTest = { status: 'idle', result: null, runId: null, contractId: null, error: null, formDraft: null };
|
|
1732
|
+
}
|
|
1733
|
+
if (!state.safeTest.runId) {
|
|
1734
|
+
let storedRunId = null;
|
|
1735
|
+
const key = safeTestStorageKey(requestedId);
|
|
1736
|
+
try { storedRunId = key ? tabStorage?.getItem(key) : null; } catch { /* storage is optional */ }
|
|
1737
|
+
if (storedRunId) {
|
|
1738
|
+
state.safeTest.runId = storedRunId;
|
|
1739
|
+
state.safeTest.contractId = requestedId;
|
|
1740
|
+
state.safeTest.result = { runId: storedRunId, status: 'queued' };
|
|
1741
|
+
await refreshSafeTestReadback();
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1281
1744
|
const authorityPayload = objectValue(payload).authority ?? objectValue(payload).authorityMatrix;
|
|
1282
1745
|
if (authorityPayload !== undefined) state.authority = { ...normalizeAuthority(authorityPayload), error: null };
|
|
1283
1746
|
const triggerPayload = objectValue(payload).trigger ?? objectValue(payload).triggers;
|
|
1284
1747
|
if (triggerPayload !== undefined) state.triggers = { ...state.triggers, status: 'ready', trigger: normalizeTrigger(triggerPayload), error: null, draft: null };
|
|
1285
1748
|
await loadTriggerCatalog(state.contract.detail.trigger?.type ?? 'manual', { renderAfter: false, enterDetail: false });
|
|
1286
1749
|
} catch (error) {
|
|
1287
|
-
setError(state.contract, error);
|
|
1750
|
+
if (state.contract.selectedId === requestedId && contractDetailRequestGeneration === requestGeneration) setError(state.contract, error);
|
|
1288
1751
|
}
|
|
1289
1752
|
if (renderAfter) { render(); notifyChange(); }
|
|
1290
1753
|
return state.contract.detail;
|
|
@@ -1439,11 +1902,21 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1439
1902
|
async function loadAuthority(id = contractId()) {
|
|
1440
1903
|
setActiveStage('authority');
|
|
1441
1904
|
if (!id) { state.authority.status = 'unknown'; render(); return state.authority; }
|
|
1905
|
+
const requestedId = id;
|
|
1906
|
+
if (state.contract.status === 'loading' || state.contract.selectedId !== requestedId || currentContract()?.contractId !== requestedId) {
|
|
1907
|
+
render();
|
|
1908
|
+
return state.authority;
|
|
1909
|
+
}
|
|
1910
|
+
const requestedVersion = currentContract()?.version;
|
|
1911
|
+
const requestGeneration = ++authorityRequestGeneration;
|
|
1442
1912
|
state.authority.status = 'loading'; state.authority.error = null; render();
|
|
1443
1913
|
try {
|
|
1444
|
-
const payload = await callApi(path('authority', { contractId:
|
|
1914
|
+
const payload = await callApi(path('authority', { contractId: requestedId, contractVersion: requestedVersion }));
|
|
1915
|
+
if (state.contract.selectedId !== requestedId || authorityRequestGeneration !== requestGeneration || currentContract()?.version !== requestedVersion) return state.authority;
|
|
1445
1916
|
state.authority = { ...state.authority, ...normalizeAuthority(payload), error: null };
|
|
1446
|
-
} catch (error) {
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
if (state.contract.selectedId === requestedId && authorityRequestGeneration === requestGeneration && currentContract()?.version === requestedVersion) setError(state.authority, error);
|
|
1919
|
+
}
|
|
1447
1920
|
render(); notifyChange(); return state.authority;
|
|
1448
1921
|
}
|
|
1449
1922
|
|
|
@@ -1509,33 +1982,43 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1509
1982
|
external_send: false,
|
|
1510
1983
|
production_writes: false,
|
|
1511
1984
|
};
|
|
1985
|
+
stopSafeTestReadback();
|
|
1986
|
+
safeTestReadCount = 0;
|
|
1987
|
+
state.safeTest.runId = null;
|
|
1988
|
+
state.safeTest.contractId = id;
|
|
1512
1989
|
state.safeTest.formDraft = body; state.safeTest.status = 'loading'; state.safeTest.error = null; render();
|
|
1513
1990
|
try {
|
|
1514
1991
|
const payload = await callMutation(path('tests', { contractId: id }), { body, key: idempotencyKey('mana-safe-test') });
|
|
1515
1992
|
const accepted = normalizeRun(payload);
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
const result = readback?.id ? readback : payload;
|
|
1521
|
-
state.safeTest.result = result;
|
|
1993
|
+
const acceptedId = accepted.id;
|
|
1994
|
+
state.safeTest.runId = acceptedId || null;
|
|
1995
|
+
state.safeTest.result = payload;
|
|
1996
|
+
rememberSafeTestRun(id, acceptedId);
|
|
1522
1997
|
state.safeTest.formDraft = null;
|
|
1523
|
-
state.safeTest.status = safeTestStatus(
|
|
1998
|
+
state.safeTest.status = safeTestStatus(payload, currentContract());
|
|
1999
|
+
if (acceptedId) await refreshSafeTestReadback();
|
|
1524
2000
|
} catch (error) { setError(state.safeTest, error); }
|
|
1525
2001
|
render(); notifyChange(); return state.safeTest;
|
|
1526
2002
|
}
|
|
1527
2003
|
|
|
1528
2004
|
async function refreshSafeTestReadback() {
|
|
1529
|
-
const runId = normalizeRun(state.safeTest.result).id;
|
|
2005
|
+
const runId = state.safeTest.runId ?? normalizeRun(state.safeTest.result).id;
|
|
1530
2006
|
if (!runId) return state.safeTest;
|
|
2007
|
+
stopSafeTestReadback();
|
|
1531
2008
|
try {
|
|
1532
2009
|
const result = normalizeRun(await callApi(path('runDetail', { runId })));
|
|
2010
|
+
if (state.safeTest.runId !== runId) return state.safeTest;
|
|
2011
|
+
if (result.id !== runId || (result.contractId && result.contractId !== contractId())) throw new Error('safe_test_readback_identity_mismatch');
|
|
1533
2012
|
state.safeTest.result = result;
|
|
1534
2013
|
state.safeTest.status = safeTestStatus(result, currentContract());
|
|
1535
2014
|
state.safeTest.error = null;
|
|
1536
|
-
|
|
2015
|
+
if (result.status === 'queued' || result.status === 'running') scheduleSafeTestReadback(runId);
|
|
2016
|
+
} catch (error) {
|
|
2017
|
+
if (state.safeTest.runId !== runId) return state.safeTest;
|
|
2018
|
+
state.safeTest.error = normalizeError(error);
|
|
1537
2019
|
state.safeTest.status = 'unknown';
|
|
1538
2020
|
}
|
|
2021
|
+
render(); notifyChange();
|
|
1539
2022
|
return state.safeTest;
|
|
1540
2023
|
}
|
|
1541
2024
|
|
|
@@ -1588,12 +2071,25 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1588
2071
|
state.runs.status = 'loading'; state.runs.error = null; render();
|
|
1589
2072
|
try {
|
|
1590
2073
|
const normalized = normalizeRuns(await callApi(path('runList', { contractId: id })));
|
|
2074
|
+
// The runtime defines run-list recency by creation time. A long-running old
|
|
2075
|
+
// run may be updated after a newer run and must not become "latest" again.
|
|
2076
|
+
normalized.records.sort((left, right) => new Date(right.startedAt ?? 0).getTime() - new Date(left.startedAt ?? 0).getTime());
|
|
1591
2077
|
state.runs = { ...state.runs, ...normalized, status: normalized.state, error: null };
|
|
1592
2078
|
const latestSafeTest = normalized.records.find((run) => run.mode === 'safe_test' && run.contractId === id);
|
|
1593
2079
|
if (latestSafeTest) {
|
|
2080
|
+
stopSafeTestReadback();
|
|
2081
|
+
state.safeTest.runId = latestSafeTest.id;
|
|
2082
|
+
state.safeTest.contractId = id;
|
|
2083
|
+
rememberSafeTestRun(id, latestSafeTest.id);
|
|
1594
2084
|
state.safeTest.result = latestSafeTest;
|
|
1595
2085
|
state.safeTest.status = safeTestStatus(latestSafeTest, currentContract());
|
|
1596
2086
|
state.safeTest.error = null;
|
|
2087
|
+
if (latestSafeTest.status === 'queued' || latestSafeTest.status === 'running') scheduleSafeTestReadback(latestSafeTest.id);
|
|
2088
|
+
}
|
|
2089
|
+
const latestRun = normalized.records[0];
|
|
2090
|
+
if (latestRun?.id) {
|
|
2091
|
+
state.runs.selectedId = latestRun.id;
|
|
2092
|
+
await loadRunDetail(latestRun.id, { renderAfter: false });
|
|
1597
2093
|
}
|
|
1598
2094
|
}
|
|
1599
2095
|
catch (error) { setError(state.runs, error); }
|
|
@@ -1643,6 +2139,280 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1643
2139
|
render(); notifyChange(); return state.runAction;
|
|
1644
2140
|
}
|
|
1645
2141
|
|
|
2142
|
+
// Profile, input, allowed-resource, and destination pickers. They exist
|
|
2143
|
+
// only when the host offers execution profiles; choices come from the
|
|
2144
|
+
// loaded connection resources, and references that are not listed now are
|
|
2145
|
+
// preserved instead of dropped.
|
|
2146
|
+
function contractProfilePickers(values, contract, fields) {
|
|
2147
|
+
const profileInput = createElement(document, 'input', { attrs: { name: 'profileId', type: 'hidden' } });
|
|
2148
|
+
profileInput.value = values.profileId;
|
|
2149
|
+
fields.profileId = profileInput;
|
|
2150
|
+
const knownProfile = (profileId) => executionProfiles.find((profile) => profile.id === profileId) ?? null;
|
|
2151
|
+
let selectedProfileId = values.profileId;
|
|
2152
|
+
const connectorById = (connectorId) => state.connections.records.find((item) => item.id === connectorId) ?? null;
|
|
2153
|
+
const resourceIsRegistered = (value) => {
|
|
2154
|
+
const resource = objectValue(value);
|
|
2155
|
+
const id = stringValue(firstPresent(resource.id, resource.resourceId, resource.resource_id, typeof value === 'string' ? value : null)).trim();
|
|
2156
|
+
const status = normalizeStatus(firstPresent(resource.status, resource.state, resource.connectionStatus, resource.connection_status));
|
|
2157
|
+
return Boolean(id) && !['failed', 'error', 'unavailable', 'permission_denied', 'forbidden', 'unauthorized'].includes(status);
|
|
2158
|
+
};
|
|
2159
|
+
const connectorHasRegisteredResource = (connector) => (connector?.resources ?? []).some(resourceIsRegistered);
|
|
2160
|
+
const profileConnectionLabel = (connector) => {
|
|
2161
|
+
if (['connected', 'verified', 'readback_verified'].includes(normalizeStatus(connector?.status))) return '接続確認済み';
|
|
2162
|
+
if (connectorHasRegisteredResource(connector)) return 'プロジェクト資源登録済み・接続未確認';
|
|
2163
|
+
if (!connector || ['unregistered', 'not_registered'].includes(normalizeStatus(connector.status))) return '未登録のため利用不可';
|
|
2164
|
+
return '接続未確認のため利用不可';
|
|
2165
|
+
};
|
|
2166
|
+
const currentReadiness = (profileId) => executionProfileReadiness(profileId, contract, [
|
|
2167
|
+
state.safeTest.result, state.runs.detail, ...state.runs.records,
|
|
2168
|
+
], executionProfiles);
|
|
2169
|
+
const profileField = createElement(document, 'fieldset', { className: 'outcome-mana-profile-picker' });
|
|
2170
|
+
profileField.append(createElement(document, 'legend', { text: '委任する仕事' }));
|
|
2171
|
+
profileField.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: '仕事の種類を選ぶと、入力資料・生成内容・保存先が決まります。アプリの接続状態と、実行できるかの事前確認は別に表示します。' }));
|
|
2172
|
+
const profileRadios = [];
|
|
2173
|
+
executionProfiles.forEach((profile) => {
|
|
2174
|
+
const inputConnectors = profile.inputConnectorIds.map(connectorById);
|
|
2175
|
+
const destinationConnector = connectorById(profile.destinationConnectorId);
|
|
2176
|
+
const readiness = currentReadiness(profile.id);
|
|
2177
|
+
const card = createElement(document, 'label', { className: `outcome-mana-profile-card${selectedProfileId === profile.id ? ' is-selected' : ''}` });
|
|
2178
|
+
const radio = createElement(document, 'input', { attrs: { type: 'radio', name: 'manaExecutionProfile', value: profile.id, 'data-profile-id': profile.id } });
|
|
2179
|
+
radio.checked = selectedProfileId === profile.id;
|
|
2180
|
+
const copy = createElement(document, 'span', { className: 'outcome-mana-profile-copy' });
|
|
2181
|
+
copy.append(
|
|
2182
|
+
createElement(document, 'strong', { text: profile.label }),
|
|
2183
|
+
createElement(document, 'small', { text: `入力: ${profile.input} → Manaが生成: ${profile.output} → 保存先: ${profile.destination}` }),
|
|
2184
|
+
);
|
|
2185
|
+
const statuses = createElement(document, 'span', { className: 'outcome-mana-profile-statuses' });
|
|
2186
|
+
statuses.append(
|
|
2187
|
+
createElement(document, 'small', { text: `入力 ${profile.inputConnectorLabel}: ${inputConnectors.map(profileConnectionLabel).join('、')}`, attrs: { 'data-profile-input-connection': profile.id } }),
|
|
2188
|
+
createElement(document, 'small', { text: `保存先 ${profile.destinationConnectorLabel}: ${profileConnectionLabel(destinationConnector)}`, attrs: { 'data-profile-destination-connection': profile.id } }),
|
|
2189
|
+
createElement(document, 'small', { text: readiness.label, attrs: { 'data-profile-readiness': profile.id, 'data-readiness': readiness.status } }),
|
|
2190
|
+
);
|
|
2191
|
+
card.append(radio, copy, statuses);
|
|
2192
|
+
listen(radio, 'change', () => {
|
|
2193
|
+
if (!radio.checked) return;
|
|
2194
|
+
selectedProfileId = profile.id;
|
|
2195
|
+
profileInput.value = profile.id;
|
|
2196
|
+
profileRadios.forEach(({ card: candidate, radio: candidateRadio }) => {
|
|
2197
|
+
candidate.className = `outcome-mana-profile-card${candidateRadio === radio ? ' is-selected' : ''}`;
|
|
2198
|
+
});
|
|
2199
|
+
inputChoiceLabels.forEach(({ choice, resource, input }) => {
|
|
2200
|
+
choice.hidden = !profile.inputConnectorIds.includes(resource.connectorId);
|
|
2201
|
+
if (!profile.inputConnectorIds.includes(resource.connectorId)) input.checked = false;
|
|
2202
|
+
});
|
|
2203
|
+
inputRefsInput.value = inputChoiceLabels
|
|
2204
|
+
.filter(({ resource, input }) => input.checked && profile.inputConnectorIds.includes(resource.connectorId))
|
|
2205
|
+
.map(({ resource }) => `${resource.id}${resource.version ? `@${resource.version}` : ''}`)
|
|
2206
|
+
.join('\n');
|
|
2207
|
+
const allowedIds = [];
|
|
2208
|
+
allowedChoiceLabels.forEach(({ choice, resource, input }) => {
|
|
2209
|
+
const usable = profile.inputConnectorIds.includes(resource.connectorId) || resource.connectorId === profile.destinationConnectorId;
|
|
2210
|
+
choice.hidden = !usable;
|
|
2211
|
+
if (!usable) input.checked = false;
|
|
2212
|
+
else if (input.checked) allowedIds.push(resource.id);
|
|
2213
|
+
});
|
|
2214
|
+
allowedResourcesInput.value = [...new Set([...allowedIds, ...preservedUnknownIds])].join('\n');
|
|
2215
|
+
destinationChoiceLabels.forEach(({ choice, resource, input }) => {
|
|
2216
|
+
choice.hidden = resource.connectorId !== profile.destinationConnectorId;
|
|
2217
|
+
if (resource.connectorId !== profile.destinationConnectorId) input.checked = false;
|
|
2218
|
+
});
|
|
2219
|
+
if (destinationResource?.connectorId !== profile.destinationConnectorId) {
|
|
2220
|
+
destinationResource = null;
|
|
2221
|
+
adapterIdInput.value = '';
|
|
2222
|
+
locationInput.value = '';
|
|
2223
|
+
}
|
|
2224
|
+
});
|
|
2225
|
+
profileRadios.push({ card, radio });
|
|
2226
|
+
profileField.append(card);
|
|
2227
|
+
});
|
|
2228
|
+
const resourceIsSelectable = (value) => {
|
|
2229
|
+
const resource = objectValue(value);
|
|
2230
|
+
const status = normalizeStatus(firstPresent(resource.status, resource.state, resource.connectionStatus, resource.connection_status));
|
|
2231
|
+
return !['failed', 'error', 'unavailable', 'permission_denied', 'forbidden', 'unauthorized'].includes(status);
|
|
2232
|
+
};
|
|
2233
|
+
const selectableResources = state.connections.records
|
|
2234
|
+
.flatMap((connector) => (connector.resources ?? []).map((value) => {
|
|
2235
|
+
const resource = objectValue(value);
|
|
2236
|
+
const id = stringValue(firstPresent(resource.id, resource.resourceId, resource.resource_id, typeof value === 'string' ? value : null)).trim();
|
|
2237
|
+
const connectorVerified = ['connected', 'verified'].includes(normalizeStatus(connector.status));
|
|
2238
|
+
if (!id || !resourceIsSelectable(value) || (!connectorVerified && !resourceIsRegistered(value))) return null;
|
|
2239
|
+
return {
|
|
2240
|
+
id,
|
|
2241
|
+
label: resourceLabel(value),
|
|
2242
|
+
connectorId: connector.id,
|
|
2243
|
+
connector: connector.label,
|
|
2244
|
+
adapterId: stringValue(firstPresent(resource.adapterId, resource.adapter_id, connector.id)).trim(),
|
|
2245
|
+
version: stringValue(firstPresent(resource.version, resource.commit, resource.sha, resource.revision)).trim() || null,
|
|
2246
|
+
};
|
|
2247
|
+
}).filter(Boolean));
|
|
2248
|
+
const selectableIds = new Set(selectableResources.map((resource) => resource.id));
|
|
2249
|
+
const inputReferenceValues = parseReferenceLines(values.inputRefs);
|
|
2250
|
+
const inputReferenceIds = inputReferenceValues.map((reference) => reference.id);
|
|
2251
|
+
const preservedInputReferences = inputReferenceValues.filter((reference) => !selectableIds.has(reference.id));
|
|
2252
|
+
const inputField = createElement(document, 'fieldset', { className: 'outcome-mana-resource-picker' });
|
|
2253
|
+
inputField.append(createElement(document, 'legend', { text: '入力資料' }));
|
|
2254
|
+
const inputRefsInput = createElement(document, 'textarea', {
|
|
2255
|
+
className: 'outcome-mana-resource-picker-value',
|
|
2256
|
+
attrs: { name: 'inputRefs', hidden: 'hidden', 'aria-hidden': 'true' },
|
|
2257
|
+
});
|
|
2258
|
+
inputRefsInput.value = values.inputRefs;
|
|
2259
|
+
fields.inputRefs = inputRefsInput;
|
|
2260
|
+
inputField.append(inputRefsInput);
|
|
2261
|
+
const inputChoiceLabels = [];
|
|
2262
|
+
if (selectableResources.length) {
|
|
2263
|
+
const choices = createElement(document, 'div', { className: 'outcome-mana-resource-choices' });
|
|
2264
|
+
const syncInputReferences = () => {
|
|
2265
|
+
const selectedProfile = knownProfile(selectedProfileId);
|
|
2266
|
+
const selected = selectableResources.filter((candidate) => candidate.inputRef?.checked
|
|
2267
|
+
&& selectedProfile?.inputConnectorIds.includes(candidate.connectorId));
|
|
2268
|
+
inputRefsInput.value = [
|
|
2269
|
+
...selected.map((candidate) => {
|
|
2270
|
+
const version = stringValue(candidate.versionInput?.value ?? candidate.version).trim();
|
|
2271
|
+
return `${candidate.id}${version ? `@${version}` : ''}`;
|
|
2272
|
+
}),
|
|
2273
|
+
...preservedInputReferences.map((reference) => `${reference.id}${reference.version ? `@${reference.version}` : ''}`),
|
|
2274
|
+
].join('\n');
|
|
2275
|
+
};
|
|
2276
|
+
selectableResources.forEach((resource) => {
|
|
2277
|
+
const input = createElement(document, 'input', { attrs: { type: 'checkbox', value: resource.id, 'data-picker': 'input-reference' } });
|
|
2278
|
+
input.checked = inputReferenceIds.includes(resource.id);
|
|
2279
|
+
listen(input, 'change', syncInputReferences);
|
|
2280
|
+
resource.inputRef = input;
|
|
2281
|
+
const choice = createElement(document, 'label', { className: 'outcome-mana-resource-choice' });
|
|
2282
|
+
choice.hidden = !knownProfile(selectedProfileId)?.inputConnectorIds.includes(resource.connectorId);
|
|
2283
|
+
inputChoiceLabels.push({ choice, resource, input });
|
|
2284
|
+
choice.append(input, createElement(document, 'span', { text: resource.label }), createElement(document, 'small', { text: `${resource.connector} · ${resource.id}${resource.version ? ` @ ${resource.version}` : ' · 版未確認'}` }));
|
|
2285
|
+
if (resource.connectorId === 'github') {
|
|
2286
|
+
const existingVersion = inputReferenceValues.find((reference) => reference.id === resource.id)?.version ?? resource.version ?? '';
|
|
2287
|
+
const versionInput = createElement(document, 'input', {
|
|
2288
|
+
className: 'outcome-mana-resource-version',
|
|
2289
|
+
attrs: {
|
|
2290
|
+
type: 'text',
|
|
2291
|
+
value: existingVersion,
|
|
2292
|
+
placeholder: '40文字のコミットSHA',
|
|
2293
|
+
maxlength: '40',
|
|
2294
|
+
'aria-label': `${resource.label}のコミットSHA`,
|
|
2295
|
+
},
|
|
2296
|
+
});
|
|
2297
|
+
versionInput.value = existingVersion;
|
|
2298
|
+
resource.versionInput = versionInput;
|
|
2299
|
+
listen(versionInput, 'input', syncInputReferences);
|
|
2300
|
+
listen(versionInput, 'change', syncInputReferences);
|
|
2301
|
+
choice.append(versionInput);
|
|
2302
|
+
}
|
|
2303
|
+
choices.append(choice);
|
|
2304
|
+
});
|
|
2305
|
+
inputField.append(choices);
|
|
2306
|
+
} else {
|
|
2307
|
+
inputField.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: '入力に使える接続済み資源がありません。' }));
|
|
2308
|
+
}
|
|
2309
|
+
if (preservedInputReferences.length) inputField.append(createElement(document, 'p', { className: 'outcome-mana-resource-preserved', text: `既存の入力参照を保持(現在は未確認): ${preservedInputReferences.map((reference) => reference.id).join('、')}` }));
|
|
2310
|
+
const allowedResourceIds = values.allowedResources.split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
|
|
2311
|
+
const preservedUnknownIds = allowedResourceIds.filter((id) => !selectableIds.has(id));
|
|
2312
|
+
const resourceField = createElement(document, 'fieldset', { className: 'outcome-mana-resource-picker' });
|
|
2313
|
+
resourceField.append(createElement(document, 'legend', { text: 'この委任で使う接続資源' }));
|
|
2314
|
+
const allowedResourcesInput = createElement(document, 'textarea', {
|
|
2315
|
+
className: 'outcome-mana-resource-picker-value',
|
|
2316
|
+
attrs: { name: 'allowedResources', hidden: 'hidden', 'aria-hidden': 'true' },
|
|
2317
|
+
});
|
|
2318
|
+
allowedResourcesInput.value = allowedResourceIds.join('\n');
|
|
2319
|
+
fields.allowedResources = allowedResourcesInput;
|
|
2320
|
+
resourceField.append(allowedResourcesInput);
|
|
2321
|
+
const allowedChoiceLabels = [];
|
|
2322
|
+
if (selectableResources.length) {
|
|
2323
|
+
const choices = createElement(document, 'div', { className: 'outcome-mana-resource-choices' });
|
|
2324
|
+
selectableResources.forEach((resource) => {
|
|
2325
|
+
const input = createElement(document, 'input', { attrs: { type: 'checkbox', value: resource.id, 'data-picker': 'allowed-resource' } });
|
|
2326
|
+
input.checked = allowedResourceIds.includes(resource.id);
|
|
2327
|
+
listen(input, 'change', () => {
|
|
2328
|
+
const selectedProfile = knownProfile(selectedProfileId);
|
|
2329
|
+
const selected = selectableResources.filter((candidate) => candidate.input.checked
|
|
2330
|
+
&& (selectedProfile?.inputConnectorIds.includes(candidate.connectorId) || candidate.connectorId === selectedProfile?.destinationConnectorId)).map((candidate) => candidate.id);
|
|
2331
|
+
allowedResourcesInput.value = [...new Set([...selected, ...preservedUnknownIds])].join('\n');
|
|
2332
|
+
if (!input.checked && destinationResource?.id === resource.id) {
|
|
2333
|
+
destinationResource = null;
|
|
2334
|
+
adapterIdInput.value = '';
|
|
2335
|
+
locationInput.value = '';
|
|
2336
|
+
const destinationRadio = destinationChoiceLabels.find((item) => item.resource.id === resource.id)?.input;
|
|
2337
|
+
if (destinationRadio) destinationRadio.checked = false;
|
|
2338
|
+
}
|
|
2339
|
+
});
|
|
2340
|
+
resource.input = input;
|
|
2341
|
+
const choice = createElement(document, 'label', { className: 'outcome-mana-resource-choice' });
|
|
2342
|
+
choice.hidden = !(knownProfile(selectedProfileId)?.inputConnectorIds.includes(resource.connectorId)
|
|
2343
|
+
|| resource.connectorId === knownProfile(selectedProfileId)?.destinationConnectorId);
|
|
2344
|
+
allowedChoiceLabels.push({ choice, resource, input });
|
|
2345
|
+
choice.append(input, createElement(document, 'span', { text: resource.label }), createElement(document, 'small', { text: `${resource.connector} · ${resource.id}` }));
|
|
2346
|
+
choices.append(choice);
|
|
2347
|
+
});
|
|
2348
|
+
resourceField.append(choices);
|
|
2349
|
+
} else {
|
|
2350
|
+
resourceField.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: catalogCopy.noSelectableResources }));
|
|
2351
|
+
}
|
|
2352
|
+
if (preservedUnknownIds.length) {
|
|
2353
|
+
resourceField.append(createElement(document, 'p', { className: 'outcome-mana-resource-preserved', text: `既存参照を保持(現在は未確認): ${preservedUnknownIds.join('、')}` }));
|
|
2354
|
+
}
|
|
2355
|
+
const destinationField = createElement(document, 'fieldset', { className: 'outcome-mana-resource-picker' });
|
|
2356
|
+
destinationField.append(createElement(document, 'legend', { text: '成果物の保存先' }));
|
|
2357
|
+
const adapterIdInput = createElement(document, 'input', { attrs: { name: 'adapterId', type: 'hidden' } });
|
|
2358
|
+
const locationInput = createElement(document, 'input', { attrs: { name: 'location', type: 'hidden' } });
|
|
2359
|
+
adapterIdInput.value = values.adapterId;
|
|
2360
|
+
locationInput.value = values.location;
|
|
2361
|
+
let destinationResource = selectableResources.find((resource) => resource.id === values.location && resource.adapterId === values.adapterId) ?? null;
|
|
2362
|
+
fields.adapterId = adapterIdInput;
|
|
2363
|
+
fields.location = locationInput;
|
|
2364
|
+
destinationField.append(adapterIdInput, locationInput);
|
|
2365
|
+
const destinationChoiceLabels = [];
|
|
2366
|
+
if (selectableResources.length) {
|
|
2367
|
+
const choices = createElement(document, 'div', { className: 'outcome-mana-resource-choices' });
|
|
2368
|
+
selectableResources.forEach((resource) => {
|
|
2369
|
+
const input = createElement(document, 'input', { attrs: { type: 'radio', name: 'artifactDestinationChoice', value: resource.id, 'data-picker': 'artifact-destination' } });
|
|
2370
|
+
input.checked = values.location === resource.id && values.adapterId === resource.adapterId;
|
|
2371
|
+
const choice = createElement(document, 'label', { className: 'outcome-mana-resource-choice' });
|
|
2372
|
+
choice.hidden = resource.connectorId !== knownProfile(selectedProfileId)?.destinationConnectorId;
|
|
2373
|
+
listen(input, 'change', () => {
|
|
2374
|
+
if (!input.checked) return;
|
|
2375
|
+
destinationResource = resource;
|
|
2376
|
+
adapterIdInput.value = resource.adapterId;
|
|
2377
|
+
locationInput.value = resource.id;
|
|
2378
|
+
const allowedInput = allowedChoiceLabels.find((item) => item.resource.id === resource.id)?.input;
|
|
2379
|
+
if (allowedInput) allowedInput.checked = true;
|
|
2380
|
+
const selectedProfile = knownProfile(selectedProfileId);
|
|
2381
|
+
const selected = allowedChoiceLabels.filter((item) => item.input.checked
|
|
2382
|
+
&& (selectedProfile?.inputConnectorIds.includes(item.resource.connectorId)
|
|
2383
|
+
|| item.resource.connectorId === selectedProfile?.destinationConnectorId)).map((item) => item.resource.id);
|
|
2384
|
+
allowedResourcesInput.value = [...new Set([...selected, ...preservedUnknownIds])].join('\n');
|
|
2385
|
+
});
|
|
2386
|
+
destinationChoiceLabels.push({ choice, resource, input });
|
|
2387
|
+
choice.append(input, createElement(document, 'span', { text: resource.label }), createElement(document, 'small', { text: `${resource.connector} · ${resource.id}` }));
|
|
2388
|
+
choices.append(choice);
|
|
2389
|
+
});
|
|
2390
|
+
destinationField.append(choices);
|
|
2391
|
+
} else {
|
|
2392
|
+
destinationField.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: '保存先に使える接続済み資源がありません。' }));
|
|
2393
|
+
}
|
|
2394
|
+
if (values.location && !selectableIds.has(values.location)) destinationField.append(createElement(document, 'p', { className: 'outcome-mana-resource-preserved', text: `既存の保存先を保持(現在は未確認): ${values.location}` }));
|
|
2395
|
+
function validate(isExisting) {
|
|
2396
|
+
const selected = knownProfile(profileInput.value);
|
|
2397
|
+
const inputReferences = parseReferenceLines(inputRefsInput.value);
|
|
2398
|
+
const allowedIds = allowedResourcesInput.value.split(/\r?\n/).map((item) => item.trim()).filter(Boolean);
|
|
2399
|
+
const reasons = profileInputValidationReasons(selected, inputReferences, selectableResources);
|
|
2400
|
+
const originalDestination = contract.artifactDestination;
|
|
2401
|
+
const preservingUnlistedDestination = isExisting && !destinationResource
|
|
2402
|
+
&& Boolean(originalDestination?.adapterId && originalDestination?.location)
|
|
2403
|
+
&& adapterIdInput.value === originalDestination.adapterId
|
|
2404
|
+
&& locationInput.value === originalDestination.location
|
|
2405
|
+
&& fields.environment.value === originalDestination.environment
|
|
2406
|
+
&& profileInput.value === contract.profileId;
|
|
2407
|
+
if (selected && !destinationResource && !preservingUnlistedDestination) reasons.push(`${selected.destinationConnectorLabel}の保存先を選んでください。`);
|
|
2408
|
+
if (destinationResource && destinationResource.connectorId !== selected?.destinationConnectorId) reasons.push('保存先アプリが選んだ仕事と一致していません。');
|
|
2409
|
+
if (destinationResource && !allowedIds.includes(destinationResource.id)) reasons.push('保存先を「この委任で使う接続資源」に含めてください。');
|
|
2410
|
+
if (!allowedIds.some((id) => selectableIds.has(id))) reasons.push('この委任で使う接続資源を1つ以上選んでください。');
|
|
2411
|
+
return reasons;
|
|
2412
|
+
}
|
|
2413
|
+
return { profileInput, profileField, inputField, resourceField, destinationField, validate };
|
|
2414
|
+
}
|
|
2415
|
+
|
|
1646
2416
|
function renderContractSection() {
|
|
1647
2417
|
const section = createElement(document, 'section', { className: 'outcome-mana-section outcome-mana-contract', attrs: { 'aria-labelledby': 'outcome-mana-contract-heading' } });
|
|
1648
2418
|
section.append(sectionHeading(document, '成果契約', state.contract.status, '期待する成果、対象範囲、根拠、保存先、完了条件を版付きで管理します。保存と有効化は別操作です。', 'outcome-mana-contract-heading'));
|
|
@@ -1667,13 +2437,18 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1667
2437
|
const form = createElement(document, 'form', { className: 'outcome-mana-form', attrs: { 'aria-label': '成果契約の編集' } });
|
|
1668
2438
|
const fields = {};
|
|
1669
2439
|
const addField = (name, label, type, options = {}) => { const result = field(document, label, type, name, values[name], options); fields[name] = result.input; form.append(result.label); };
|
|
2440
|
+
const pickers = profilePicker ? contractProfilePickers(values, contract, fields) : null;
|
|
2441
|
+
if (pickers) form.append(pickers.profileInput, pickers.profileField);
|
|
1670
2442
|
addField('outcome', '期待する成果', 'textarea', { attrs: { rows: '4', required: 'required', maxlength: '2000' }, helper: '手順ではなく、受け取りたい状態を具体的に書きます。' });
|
|
1671
2443
|
addField('scope', '対象範囲', 'textarea', { attrs: { rows: '3', required: 'required', maxlength: '2000' }, helper: '対象プロジェクトや資料の範囲。サーバーでも検証します。' });
|
|
1672
|
-
addField('inputRefs', '入力資料(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', required: 'required', maxlength: '4000' } });
|
|
2444
|
+
if (!pickers) addField('inputRefs', '入力資料(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', required: 'required', maxlength: '4000' } });
|
|
1673
2445
|
addField('knowledgeRefs', '参照する知識(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', maxlength: '4000' } });
|
|
1674
2446
|
addField('judgmentRefs', '参照する判断(ID@版、1行1件)', 'textarea', { attrs: { rows: '3', maxlength: '4000' } });
|
|
1675
|
-
|
|
1676
|
-
|
|
2447
|
+
if (pickers) form.append(pickers.inputField, pickers.resourceField, pickers.destinationField);
|
|
2448
|
+
else {
|
|
2449
|
+
addField('adapterId', '成果物アダプター', 'input', { attrs: { required: 'required', maxlength: '128' } });
|
|
2450
|
+
addField('location', '成果物の保存先', 'input', { attrs: { required: 'required', maxlength: '2000' } });
|
|
2451
|
+
}
|
|
1677
2452
|
addField('environment', '保存環境', 'select', { options: [{ value: 'isolated', label: '隔離保存先' }, { value: 'production', label: '本番保存先' }], helper: '隔離試験は本番保存先へ書き込みません。' });
|
|
1678
2453
|
addField('completionCriteria', '完了条件(id・説明・kind・expected、タブ区切り)', 'textarea', { attrs: { rows: '5', required: 'required', maxlength: '8000' }, helper: '例: criteria-1[Tab]本文を含む[Tab]content_includes[Tab]報告' });
|
|
1679
2454
|
addField('ownerActorId', '責任者の主体ID', 'input', { attrs: { required: 'required', maxlength: '128' }, helper: '主体はサーバーで現在の組織権限と照合します。' });
|
|
@@ -1693,6 +2468,13 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1693
2468
|
const isExisting = Boolean(contract.contractId);
|
|
1694
2469
|
const save = button(document, isExisting ? '下書きを保存' : '下書きを作成', async () => {
|
|
1695
2470
|
if (typeof form.reportValidity === 'function' && !form.reportValidity()) return;
|
|
2471
|
+
const reasons = pickers ? pickers.validate(isExisting) : [];
|
|
2472
|
+
if (reasons.length) {
|
|
2473
|
+
let error = Array.from(form.children).find((child) => child.hasAttribute?.('data-profile-form-error'));
|
|
2474
|
+
if (!error) { error = createElement(document, 'p', { className: 'outcome-mana-inline-error', attrs: { role: 'alert', 'data-profile-form-error': 'true' } }); form.append(error); }
|
|
2475
|
+
error.textContent = reasons.join(' ');
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
1696
2478
|
await saveContractDraft(contractDraftFromForm(fields), { create: !isExisting });
|
|
1697
2479
|
}, { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !manage || state.contract.status === 'saving', attrs: { 'aria-label': isExisting ? '成果契約の下書きを保存' : '成果契約の下書きを作成' } });
|
|
1698
2480
|
actions.append(save);
|
|
@@ -1713,6 +2495,19 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1713
2495
|
else if (['loading', 'empty', 'permission_denied', 'error_retryable', 'conflict', 'unknown'].includes(state.authority.status)) {
|
|
1714
2496
|
const message = stateMessage(document, state.authority.status, { emptyTitle: '権限設定は未登録です', emptyText: '上流の権限設定を確認してから追加します。', message: state.authority.error?.message, retry: () => loadAuthority() });
|
|
1715
2497
|
if (message) content.append(message);
|
|
2498
|
+
if (state.authority.status === 'empty' && canManage(currentSession())) {
|
|
2499
|
+
const form = createElement(document, 'form', { className: 'outcome-mana-form', attrs: { 'aria-label': '最初の権限設定を追加' } });
|
|
2500
|
+
const operation = field(document, '許可する操作', 'input', 'authorityOperation', '', { attrs: { required: 'required', maxlength: '128' }, helper: '例: github.contents.read。実行時の操作IDと一致させます。' });
|
|
2501
|
+
const resource = field(document, '対象資源ID', 'input', 'authorityResource', '', { attrs: { required: 'required', maxlength: '256' }, helper: '接続資源の安定IDを指定します。表示名やURLでは判定しません。' });
|
|
2502
|
+
const policy = field(document, '実行方針', 'select', 'authorityPolicy', 'approval', { options: Object.entries(AUTHORITY_LABELS).map(([value, label]) => ({ value, label })) });
|
|
2503
|
+
form.append(operation.label, resource.label, policy.label);
|
|
2504
|
+
form.append(button(document, '最初の権限を追加', () => {
|
|
2505
|
+
if (typeof form.reportValidity === 'function' && !form.reportValidity()) return;
|
|
2506
|
+
saveAuthority([{ operation: operation.input.value.trim(), resource: resource.input.value.trim(), policy: policy.input.value }]);
|
|
2507
|
+
}, { className: 'outcome-mana-button outcome-mana-button-primary' }));
|
|
2508
|
+
listen(form, 'submit', (event) => event.preventDefault?.());
|
|
2509
|
+
content.append(form);
|
|
2510
|
+
}
|
|
1716
2511
|
} else {
|
|
1717
2512
|
const matrix = state.authority.draft ?? state.authority.matrix;
|
|
1718
2513
|
const table = createElement(document, 'div', { className: 'outcome-mana-authority-table', attrs: { role: 'table', 'aria-label': '操作権限マトリクス' } });
|
|
@@ -1787,9 +2582,30 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1787
2582
|
form.append(actions); listen(form, 'submit', (event) => event.preventDefault?.()); content.append(form); section.append(content); return section;
|
|
1788
2583
|
}
|
|
1789
2584
|
|
|
2585
|
+
function renderPreflightChecks(preflight) {
|
|
2586
|
+
if (!preflight?.checks?.length) return null;
|
|
2587
|
+
const section = createElement(document, 'section');
|
|
2588
|
+
section.append(createElement(document, 'h4', { text: '実行前チェック' }));
|
|
2589
|
+
const checks = createElement(document, 'div', { className: 'outcome-mana-preflight-list', attrs: { role: 'list', 'aria-label': '実行前チェック' } });
|
|
2590
|
+
preflight.checks.forEach((check) => {
|
|
2591
|
+
const item = createElement(document, 'article', { className: 'outcome-mana-preflight-item', attrs: { role: 'listitem' } });
|
|
2592
|
+
item.append(createElement(document, 'strong', { text: check.id }), statusBadge(document, check.status));
|
|
2593
|
+
item.append(definitionList(document, [
|
|
2594
|
+
['不足理由', PREFLIGHT_REASON_LABELS[check.reason] ?? check.reason],
|
|
2595
|
+
['必要な対応', PREFLIGHT_ACTION_LABELS[check.action] ?? check.action],
|
|
2596
|
+
['対応後の再開地点', STAGE_LABELS[check.resumeFrom] ?? check.resumeFrom],
|
|
2597
|
+
]));
|
|
2598
|
+
checks.append(item);
|
|
2599
|
+
});
|
|
2600
|
+
section.append(checks);
|
|
2601
|
+
return section;
|
|
2602
|
+
}
|
|
2603
|
+
|
|
1790
2604
|
function renderSafeTestSection() {
|
|
1791
2605
|
const section = createElement(document, 'section', { className: 'outcome-mana-section', attrs: { 'aria-labelledby': 'outcome-mana-safe-test-heading' } });
|
|
1792
2606
|
const contract = currentContract();
|
|
2607
|
+
const inFlight = Boolean(state.safeTest.runId && state.safeTest.status !== 'unknown'
|
|
2608
|
+
&& ['queued', 'running'].includes(normalizeRun(state.safeTest.result).status));
|
|
1793
2609
|
const effectiveTestStatus = state.safeTest.status === 'loading' ? 'loading' : state.safeTest.result
|
|
1794
2610
|
? safeTestStatus(state.safeTest.result, contract) : state.safeTest.status;
|
|
1795
2611
|
section.append(sectionHeading(document, '隔離試験', effectiveTestStatus, '契約版とサンプル入力で試験します。外部送信と本番書き込みはリクエストでも遮断し、未実行の操作を成功とは表示しません。', 'outcome-mana-safe-test-heading'));
|
|
@@ -1802,25 +2618,50 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1802
2618
|
form.append(version.label, sample.label);
|
|
1803
2619
|
const safety = createElement(document, 'p', { className: 'outcome-mana-safe-note', text: '隔離モード: 外部送信なし / 本番書き込みなし / 成果物は隔離保存先のみ' }); form.append(safety);
|
|
1804
2620
|
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
1805
|
-
actions.append(button(document, '
|
|
2621
|
+
actions.append(button(document, inFlight ? '隔離試験を実行中' : '新しい隔離試験を実行', () => runSafeTest({ contractVersion: Number(version.input.value), sampleInput: sample.input.value }), { className: 'outcome-mana-button outcome-mana-button-primary', disabled: !canManage(currentSession()) || state.safeTest.status === 'loading' || inFlight }));
|
|
1806
2622
|
form.append(actions); listen(form, 'submit', (event) => event.preventDefault?.()); content.append(form);
|
|
1807
|
-
if (
|
|
1808
|
-
|
|
2623
|
+
if (inFlight) {
|
|
2624
|
+
content.append(createElement(document, 'p', { text: '隔離試験を実行中です。同じ試験IDの結果を追跡しています。', attrs: { role: 'status' } }));
|
|
2625
|
+
content.append(button(document, '結果を再取得', refreshSafeTestReadback, { className: 'outcome-mana-button outcome-mana-button-secondary' }));
|
|
2626
|
+
}
|
|
2627
|
+
if (!inFlight && effectiveTestStatus === 'unexecuted') content.append(stateMessage(document, 'unknown', { message: '試験要求は受理されましたが、実行結果を確認できていません。成功とは扱いません。', retry: state.safeTest.runId ? refreshSafeTestReadback : null, retryLabel: '結果を再取得' }));
|
|
2628
|
+
if (!inFlight && state.safeTest.status === 'unknown' && state.safeTest.runId) content.append(stateMessage(document, 'unknown', { message: '結果の取得を確認できません。同じ試験IDを再取得してください。', retry: refreshSafeTestReadback, retryLabel: '結果を再取得' }));
|
|
1809
2629
|
if (effectiveTestStatus === 'verified') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-success', text: 'この設定で試験済み', attrs: { role: 'status' } }));
|
|
1810
2630
|
if (effectiveTestStatus === 'stale') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '設定変更のため再試験が必要', attrs: { role: 'status' } }));
|
|
1811
|
-
if (effectiveTestStatus === 'completion_unverified') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '試験は実行済みですが、完了条件の検証結果を確認できていません。', attrs: { role: 'status' } }));
|
|
2631
|
+
if (!inFlight && state.safeTest.status !== 'unknown' && effectiveTestStatus === 'completion_unverified') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '試験は実行済みですが、完了条件の検証結果を確認できていません。', attrs: { role: 'status' } }));
|
|
1812
2632
|
if (effectiveTestStatus === 'configuration_snapshot_missing') content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '試験時の設定記録が不足しているため、現在の設定と一致するか確認できません。再試験が必要です。', attrs: { role: 'status' } }));
|
|
1813
|
-
if (effectiveTestStatus === 'failed')
|
|
1814
|
-
|
|
2633
|
+
if (effectiveTestStatus === 'failed') {
|
|
2634
|
+
content.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: '隔離試験が失敗しました。外部への副作用は成功扱いにしていません。', attrs: { role: 'alert' } }));
|
|
2635
|
+
}
|
|
2636
|
+
if (state.safeTest.error) {
|
|
2637
|
+
const error = state.safeTest.error;
|
|
2638
|
+
content.append(definitionList(document, [
|
|
2639
|
+
['エラーコード', error.code || UNKNOWN],
|
|
2640
|
+
['HTTP状態', error.status == null ? UNKNOWN : String(error.status)],
|
|
2641
|
+
['理由', error.message || UNKNOWN],
|
|
2642
|
+
]));
|
|
2643
|
+
}
|
|
1815
2644
|
if (state.safeTest.result) {
|
|
1816
2645
|
const result = objectValue(state.safeTest.result);
|
|
1817
2646
|
const execution = objectValue(result.execution ?? result.testRun ?? result.run);
|
|
1818
|
-
|
|
2647
|
+
const run = normalizeRun(state.safeTest.result);
|
|
2648
|
+
content.append(definitionList(document, [
|
|
2649
|
+
['完了判定', statusLabel(completionState(state.safeTest.result))],
|
|
2650
|
+
['Mana実行状態', statusLabel(run.status)],
|
|
2651
|
+
['試験ID', firstPresent(state.safeTest.runId, run.id, result.test_id, result.testId, result.id, execution.id)],
|
|
2652
|
+
['対象版', firstPresent(run.contractVersion, result.contract_version, result.contractVersion, contract.version)],
|
|
2653
|
+
...(run.stage ? [['現在の段階', STAGE_LABELS[run.stage] ?? run.stage]] : []),
|
|
2654
|
+
...(run.errorCode ? [['エラーコード', run.errorCode]] : []),
|
|
2655
|
+
...(run.errorMessage ? [['理由', run.errorMessage]] : []),
|
|
2656
|
+
['隔離成果物の読戻し', artifactReadbackSummary(run.artifactReadback)],
|
|
2657
|
+
]));
|
|
2658
|
+
const preflight = renderPreflightChecks(run.preflight);
|
|
2659
|
+
if (preflight) content.append(preflight);
|
|
1819
2660
|
content.append(evidenceList(document, '予定した操作', result.operations ?? execution.operations));
|
|
1820
2661
|
content.append(evidenceList(document, '必要な承認', result.approvals ?? execution.approvals));
|
|
1821
2662
|
content.append(evidenceList(document, '参照した根拠', result.evidence ?? execution.evidence));
|
|
1822
|
-
content.append(evidenceList(document, '隔離成果物',
|
|
1823
|
-
content.append(evidenceList(document, '完了条件の評価',
|
|
2663
|
+
content.append(evidenceList(document, '隔離成果物', run.artifacts));
|
|
2664
|
+
content.append(evidenceList(document, '完了条件の評価', criterionSummaries(run.completion.criteria)));
|
|
1824
2665
|
}
|
|
1825
2666
|
section.append(content); return section;
|
|
1826
2667
|
}
|
|
@@ -1844,6 +2685,8 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1844
2685
|
['更新', dateLabel(detail.updatedAt)],
|
|
1845
2686
|
]));
|
|
1846
2687
|
section.append(stageList(document, detail.stages));
|
|
2688
|
+
const preflight = renderPreflightChecks(detail.preflight);
|
|
2689
|
+
if (preflight) section.append(preflight);
|
|
1847
2690
|
section.append(evidenceList(document, '参照した根拠', detail.evidence), evidenceList(document, '成果物', detail.artifacts), evidenceList(document, '完了条件', detail.completion.criteria));
|
|
1848
2691
|
if (detail.completion.reason) section.append(createElement(document, 'p', { className: 'outcome-mana-inline-error', text: detail.completion.reason, attrs: { role: 'alert' } }));
|
|
1849
2692
|
const actions = createElement(document, 'div', { className: 'outcome-mana-actions' });
|
|
@@ -1919,12 +2762,13 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
1919
2762
|
|
|
1920
2763
|
function renderOverviewView() {
|
|
1921
2764
|
const view = createElement(document, 'section', { className: 'outcome-mana-overview', attrs: { 'aria-labelledby': 'outcome-mana-overview-heading' } });
|
|
1922
|
-
function startNewDelegation() {
|
|
2765
|
+
async function startNewDelegation() {
|
|
1923
2766
|
state.contract.detail = null;
|
|
1924
2767
|
state.contract.selectedId = null;
|
|
1925
2768
|
state.contract.formDraft = {};
|
|
1926
2769
|
setActiveStage('contract');
|
|
1927
2770
|
render();
|
|
2771
|
+
await ensureConnectionsForDelegation();
|
|
1928
2772
|
}
|
|
1929
2773
|
const heading = createElement(document, 'div', { className: 'outcome-mana-view-heading' });
|
|
1930
2774
|
heading.append(group(document, 'div', {}, [
|
|
@@ -2019,7 +2863,11 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2019
2863
|
['責任者', present(selected.owner?.actorId)], ['対象範囲', present(selected.scope)], ['起動条件', triggerLabel(selected.trigger?.type)],
|
|
2020
2864
|
['契約版', selected.version === null ? UNKNOWN : `v${selected.version}`], ['最終実行', state.runs.records[0] ? dateLabel(state.runs.records[0].updatedAt ?? state.runs.records[0].startedAt) : UNKNOWN],
|
|
2021
2865
|
]));
|
|
2022
|
-
inspector.append(button(document, '委任設定を開く', () => {
|
|
2866
|
+
inspector.append(button(document, '委任設定を開く', async () => {
|
|
2867
|
+
setActiveView('delegation_settings');
|
|
2868
|
+
render();
|
|
2869
|
+
await ensureConnectionsForDelegation();
|
|
2870
|
+
}, { className: 'outcome-mana-button outcome-mana-button-primary' }));
|
|
2023
2871
|
}
|
|
2024
2872
|
layout.append(list, inspector); view.append(layout); return view;
|
|
2025
2873
|
}
|
|
@@ -2030,8 +2878,10 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2030
2878
|
const values = Array.isArray(source) ? source : Object.entries(objectValue(source)).map(([id, value]) => ({ id, ...objectValue(value) }));
|
|
2031
2879
|
const byId = new Map(values.map((item) => [normalizeConnector(item).id.toLowerCase(), normalizeConnector(item)]));
|
|
2032
2880
|
const registeredById = new Map(connectorRecordsFromProject().map((item) => [item.id.toLowerCase(), item]));
|
|
2033
|
-
|
|
2034
|
-
|
|
2881
|
+
// With a host connection catalog this is the project-resource list only;
|
|
2882
|
+
// host connection status never makes a project resource look registered.
|
|
2883
|
+
const loadedById = new Map(connectionCatalog ? [] : state.connections.records.map((item) => [item.id.toLowerCase(), item]));
|
|
2884
|
+
return connectorDefinitions.map((definition) => ({
|
|
2035
2885
|
...definition,
|
|
2036
2886
|
...(registeredById.get(definition.id) ?? {}),
|
|
2037
2887
|
...(byId.get(definition.id) ?? {}),
|
|
@@ -2047,12 +2897,21 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2047
2897
|
const headingActions = createElement(document, 'div', { className: 'outcome-mana-toolbar' });
|
|
2048
2898
|
headingActions.append(button(document, state.connections.status === 'loading' ? '確認中…' : '接続を再確認', () => loadConnections(), { className: 'outcome-mana-button outcome-mana-button-secondary', disabled: state.connections.status === 'loading' }));
|
|
2049
2899
|
view.append(group(document, 'div', { className: 'outcome-mana-view-heading' }, [headingCopy, headingActions]));
|
|
2050
|
-
|
|
2900
|
+
// A host connection catalog separates host-authenticated connections,
|
|
2901
|
+
// this project's registered resources, and the delegation's allowed range.
|
|
2902
|
+
const catalogMode = Boolean(connectionCatalog);
|
|
2903
|
+
const connectors = !catalogMode
|
|
2904
|
+
? projectConnectors()
|
|
2905
|
+
: state.connections.records.length ? state.connections.records : catalogPlaceholders();
|
|
2051
2906
|
if (!state.connections.selectedId && connectors.length) state.connections.selectedId = connectors[0].id;
|
|
2052
2907
|
const selectedConnector = connectors.find((item) => item.id === state.connections.selectedId) ?? null;
|
|
2908
|
+
if (catalogMode) {
|
|
2909
|
+
view.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: catalogCopy.connectionsTitle }));
|
|
2910
|
+
view.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: catalogCopy.connectionsDescription }));
|
|
2911
|
+
}
|
|
2053
2912
|
const table = createElement(document, 'div', { className: 'outcome-mana-connection-table', attrs: { role: 'table', 'aria-label': 'Mana組織接続一覧' } });
|
|
2054
2913
|
const header = createElement(document, 'div', { className: 'outcome-mana-connection-row outcome-mana-connection-header', attrs: { role: 'row' } });
|
|
2055
|
-
['接続', '状態', 'アカウント', '登録資源', '最終確認'].forEach((label) => header.append(createElement(document, 'span', { text: label, attrs: { role: 'columnheader' } })));
|
|
2914
|
+
['接続', '状態', 'アカウント', catalogMode ? 'このプロジェクトで利用' : '登録資源', '最終確認'].forEach((label) => header.append(createElement(document, 'span', { text: label, attrs: { role: 'columnheader' } })));
|
|
2056
2915
|
table.append(header);
|
|
2057
2916
|
for (const connector of connectors) {
|
|
2058
2917
|
const selected = connector.id === state.connections.selectedId;
|
|
@@ -2065,28 +2924,58 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2065
2924
|
connectorIdentity(document, connector, { attrs: { role: 'cell' } }),
|
|
2066
2925
|
group(document, 'span', { attrs: { role: 'cell' } }, [statusBadge(document, connector.status ?? 'unknown', connectionStatusLabel(connector.status))]),
|
|
2067
2926
|
createElement(document, 'span', { text: present(connector.account), attrs: { role: 'cell' } }),
|
|
2068
|
-
createElement(document, 'span', {
|
|
2927
|
+
createElement(document, 'span', {
|
|
2928
|
+
text: catalogMode
|
|
2929
|
+
? projectResourceStatusLabel(projectConnectors().find((item) => item.id === connector.id) ?? {})
|
|
2930
|
+
: connector.resources?.length ? connector.resources.map(resourceLabel).join('、') : UNKNOWN,
|
|
2931
|
+
attrs: { role: 'cell' },
|
|
2932
|
+
}),
|
|
2069
2933
|
createElement(document, 'span', { text: dateLabel(connector.checkedAt), attrs: { role: 'cell' } }),
|
|
2070
2934
|
);
|
|
2071
2935
|
table.append(row);
|
|
2072
2936
|
}
|
|
2937
|
+
if (catalogMode) {
|
|
2938
|
+
view.append(table);
|
|
2939
|
+
const resourcesSection = createElement(document, 'section', { className: 'outcome-mana-resource-section', attrs: { 'aria-label': 'プロジェクト資源' } });
|
|
2940
|
+
resourcesSection.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '2. このプロジェクトで使う資源' }));
|
|
2941
|
+
resourcesSection.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: catalogCopy.resourcesDescription }));
|
|
2942
|
+
const resourceGrid = createElement(document, 'div', { className: 'outcome-mana-project-resource-grid' });
|
|
2943
|
+
for (const connector of projectConnectors()) {
|
|
2944
|
+
const card = createElement(document, 'article', { className: 'outcome-mana-project-resource-card' });
|
|
2945
|
+
card.append(connectorIdentity(document, connector));
|
|
2946
|
+
card.append(definitionList(document, [
|
|
2947
|
+
['状態', projectResourceStatusLabel(connector)],
|
|
2948
|
+
['接続元', connectionSourceLabel(connector.id)],
|
|
2949
|
+
]));
|
|
2950
|
+
card.append(evidenceList(document, '登録資源', connector.resources?.length ? connector.resources : (connector.account ? [connector.account] : [])));
|
|
2951
|
+
resourceGrid.append(card);
|
|
2952
|
+
}
|
|
2953
|
+
resourcesSection.append(resourceGrid);
|
|
2954
|
+
view.append(resourcesSection);
|
|
2955
|
+
}
|
|
2073
2956
|
const contract = currentContract();
|
|
2074
2957
|
const inspector = createElement(document, 'aside', { className: 'outcome-mana-connection-inspector', attrs: { 'aria-label': '選択した接続の詳細' } });
|
|
2075
2958
|
inspector.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '選択中の接続' }));
|
|
2076
2959
|
if (selectedConnector) {
|
|
2077
2960
|
inspector.append(connectorIdentity(document, selectedConnector, { large: true }));
|
|
2078
2961
|
inspector.append(definitionList(document, [['接続状態', connectionStatusLabel(selectedConnector.status)], ['接続先', selectedConnector.account], ['確認日時', dateLabel(selectedConnector.checkedAt)], ['確認理由', connectionReasonLabel(selectedConnector.reason)]]));
|
|
2079
|
-
|
|
2962
|
+
if (catalogMode) {
|
|
2963
|
+
inspector.append(createElement(document, 'p', { className: 'outcome-mana-muted', text: catalogCopy.manageNote }));
|
|
2964
|
+
if (typeof options.onManageConnection === 'function') {
|
|
2965
|
+
inspector.append(button(document, catalogCopy.manageAction, () => options.onManageConnection(selectedConnector.id), { className: 'outcome-mana-button outcome-mana-button-primary' }));
|
|
2966
|
+
}
|
|
2967
|
+
} else inspector.append(evidenceList(document, '登録資源', selectedConnector.resources));
|
|
2080
2968
|
} else inspector.append(stateMessage(document, 'unknown', { message: '接続を選ぶと登録資源と確認状態を表示します。' }));
|
|
2081
2969
|
const allowed = createElement(document, 'aside', { className: 'outcome-mana-resource-panel' });
|
|
2082
|
-
allowed.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: '委任単位の許可範囲' }), createElement(document, 'h3', { text: 'この委任が利用できる資源' }));
|
|
2970
|
+
allowed.append(createElement(document, 'span', { className: 'outcome-mana-eyebrow', text: catalogMode ? '3. 委任単位の許可範囲' : '委任単位の許可範囲' }), createElement(document, 'h3', { text: 'この委任が利用できる資源' }));
|
|
2083
2971
|
if (!contract) allowed.append(stateMessage(document, 'unknown', { message: '委任を選ぶと、契約版に固定された参照先と保存先を確認できます。' }));
|
|
2084
2972
|
else {
|
|
2085
2973
|
const permitted = permittedResources(contract);
|
|
2086
2974
|
allowed.append(evidenceList(document, '許可リソース', permitted));
|
|
2087
2975
|
allowed.append(evidenceList(document, '入力', contract.inputRefs), evidenceList(document, '知識', contract.knowledgeRefs), evidenceList(document, '判断', contract.judgmentRefs), definitionList(document, [['保存先', present(contract.artifactDestination?.location)], ['接続ID', present(contract.artifactDestination?.adapterId)], ['環境', present(contract.artifactDestination?.environment)]]));
|
|
2088
2976
|
}
|
|
2089
|
-
view.append(table
|
|
2977
|
+
if (!catalogMode) view.append(table);
|
|
2978
|
+
view.append(group(document, 'div', { className: 'outcome-mana-connection-detail-layout' }, [inspector, allowed])); return view;
|
|
2090
2979
|
}
|
|
2091
2980
|
|
|
2092
2981
|
function renderDelegationSettings() {
|
|
@@ -2214,7 +3103,14 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2214
3103
|
for (const [index, stage] of WORKFLOW_STAGES.entries()) {
|
|
2215
3104
|
const item = createElement(document, 'li', { className: `outcome-mana-stage-item${activeStage === stage.id ? ' is-active' : ''}` });
|
|
2216
3105
|
const status = stageStatus(stage.id);
|
|
2217
|
-
const stageButton = button(document, '', () => {
|
|
3106
|
+
const stageButton = button(document, '', () => {
|
|
3107
|
+
if (stage.id === 'authority' && state.authority.status === 'idle' && state.contract.status === 'ready' && currentContract()?.contractId === state.contract.selectedId) {
|
|
3108
|
+
void loadAuthority();
|
|
3109
|
+
return;
|
|
3110
|
+
}
|
|
3111
|
+
setActiveStage(stage.id);
|
|
3112
|
+
render();
|
|
3113
|
+
}, {
|
|
2218
3114
|
className: 'outcome-mana-stage-button',
|
|
2219
3115
|
attrs: {
|
|
2220
3116
|
'aria-current': activeStage === stage.id ? 'step' : 'false',
|
|
@@ -2297,6 +3193,7 @@ export function createManaOutcomeUI(options = {}) {
|
|
|
2297
3193
|
saveAuthority,
|
|
2298
3194
|
saveTriggers,
|
|
2299
3195
|
runSafeTest,
|
|
3196
|
+
refreshSafeTestReadback,
|
|
2300
3197
|
activate,
|
|
2301
3198
|
stopActivation,
|
|
2302
3199
|
loadRuns,
|