@asc-agent/runtime 0.3.2 → 0.5.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/README.md +1 -1
- package/dist/adapters/claude-code/install.d.ts +7 -0
- package/dist/adapters/claude-code/install.js +114 -38
- package/dist/adapters/claude-code/session-start.d.ts +20 -0
- package/dist/adapters/claude-code/session-start.js +111 -0
- package/dist/adapters/fixture-surface/index.d.ts +33 -0
- package/dist/adapters/fixture-surface/index.js +97 -0
- package/dist/adapters/gitlab/adapter.js +1 -0
- package/dist/adapters/gitlab/client.d.ts +16 -2
- package/dist/adapters/gitlab/client.js +27 -0
- package/dist/adapters/gitlab/coordination.d.ts +19 -0
- package/dist/adapters/gitlab/coordination.js +111 -0
- package/dist/adapters/gitlab/ports.d.ts +6 -0
- package/dist/adapters/gitlab/ports.js +29 -2
- package/dist/adapters/jam/adapter.d.ts +46 -9
- package/dist/adapters/jam/adapter.js +88 -22
- package/dist/adapters/jam/setup.d.ts +62 -0
- package/dist/adapters/jam/setup.js +85 -0
- package/dist/adapters/local/canonical.d.ts +20 -0
- package/dist/adapters/local/canonical.js +58 -0
- package/dist/adapters/markdown/state-store.js +2 -1
- package/dist/adapters/service/launchd.d.ts +12 -0
- package/dist/adapters/service/launchd.js +97 -0
- package/dist/adapters/service/schtasks.d.ts +16 -0
- package/dist/adapters/service/schtasks.js +67 -0
- package/dist/adapters/service/systemd-user.d.ts +15 -0
- package/dist/adapters/service/systemd-user.js +98 -0
- package/dist/adapters/webhook/ingress.d.ts +2 -2
- package/dist/cli/asc.js +1080 -145
- package/dist/composition/registry.js +11 -3
- package/dist/composition/runtime.d.ts +36 -0
- package/dist/composition/runtime.js +80 -1
- package/dist/core/attach/setup-plan.d.ts +66 -2
- package/dist/core/attach/setup-plan.js +50 -0
- package/dist/core/binding/types.d.ts +1 -1
- package/dist/core/binding/types.js +1 -0
- package/dist/core/distribution/external-command.d.ts +26 -0
- package/dist/core/distribution/external-command.js +59 -2
- package/dist/core/distribution/persistent-runtime.d.ts +82 -0
- package/dist/core/distribution/persistent-runtime.js +49 -0
- package/dist/core/distribution/release.d.ts +3 -3
- package/dist/core/distribution/release.js +1 -1
- package/dist/core/runtime/background.d.ts +104 -0
- package/dist/core/runtime/background.js +225 -0
- package/dist/core/runtime/coordination.d.ts +312 -0
- package/dist/core/runtime/coordination.js +247 -0
- package/dist/core/runtime/front.d.ts +58 -0
- package/dist/core/runtime/front.js +50 -1
- package/dist/core/runtime/publish.d.ts +107 -0
- package/dist/core/runtime/publish.js +153 -0
- package/dist/core/runtime/workspaces.d.ts +61 -0
- package/dist/core/runtime/workspaces.js +82 -0
- package/dist/core/workspace/resolve.d.ts +36 -0
- package/dist/core/workspace/resolve.js +110 -3
- package/dist/ports/adapter.d.ts +12 -0
- package/dist/ports/coordination-surface.d.ts +59 -0
- package/dist/ports/coordination-surface.js +16 -0
- package/dist/ports/resource-context.d.ts +5 -0
- package/dist/schemas/profile.d.ts +6 -6
- package/package.json +1 -1
|
@@ -28,21 +28,29 @@ export async function composeBindings(input) {
|
|
|
28
28
|
const adapters = input.adapters ?? defaultAdapters();
|
|
29
29
|
const bindings = [];
|
|
30
30
|
const runtimes = [];
|
|
31
|
+
// Profile 이 선언한 것을 발견 단계에 알려 준다. adapter 가 지역 흔적을 못 찾아도
|
|
32
|
+
// 사람이 적어 둔 결정은 후보가 될 수 있다 — 되는지는 여전히 probe 가 정한다 (C-09 §3.1).
|
|
33
|
+
const context = {
|
|
34
|
+
...input.context,
|
|
35
|
+
...(input.roles?.length
|
|
36
|
+
? { declared: input.roles.map((role) => ({ adapterId: role.adapterId, resource: role.resource })) }
|
|
37
|
+
: {}),
|
|
38
|
+
};
|
|
31
39
|
for (const adapter of adapters) {
|
|
32
40
|
// 도구가 쓸 수 있는가와 이 프로젝트가 그 도구에 붙어 있는가는 다른 사실이다.
|
|
33
41
|
// 합치면 사람이 "설치할 일인지 붙일 일인지"를 알 수 없다.
|
|
34
42
|
if (adapter.runtime) {
|
|
35
43
|
const status = await adapter
|
|
36
|
-
.runtime(
|
|
44
|
+
.runtime(context)
|
|
37
45
|
.catch((error) => ({ state: 'UNAVAILABLE', detail: String(error) }));
|
|
38
46
|
runtimes.push({ adapterId: adapter.describe().id, ...status });
|
|
39
47
|
}
|
|
40
|
-
const candidates = await adapter.discover(
|
|
48
|
+
const candidates = await adapter.discover(context).catch(() => []);
|
|
41
49
|
for (const candidate of candidates) {
|
|
42
50
|
// probe가 터지는 것과 "안 된다"는 다르다. 예외를 UNAVAILABLE로 옮겨 적되
|
|
43
51
|
// 이유를 남긴다 — 조용히 후보에서 빼면 왜 안 보이는지 알 수 없다.
|
|
44
52
|
const result = await adapter
|
|
45
|
-
.probe(candidate,
|
|
53
|
+
.probe(candidate, context)
|
|
46
54
|
.catch((error) => ({ state: 'UNAVAILABLE', detail: String(error) }));
|
|
47
55
|
const role = input.roles?.find((r) => r.adapterId === candidate.adapterId && r.resource === candidate.resource)?.role;
|
|
48
56
|
bindings.push({
|
|
@@ -4,12 +4,15 @@ import type { EventSource } from '../ports/event-source.ts';
|
|
|
4
4
|
import type { InventoryPort } from '../ports/inventory.ts';
|
|
5
5
|
import type { ResourceContextPort } from '../ports/resource-context.ts';
|
|
6
6
|
import type { ScmPort } from '../ports/scm.ts';
|
|
7
|
+
import type { CoordinationSurfacePort } from '../ports/coordination-surface.ts';
|
|
7
8
|
export type RuntimePorts = {
|
|
8
9
|
eventSource?: EventSource;
|
|
9
10
|
scm?: ScmPort;
|
|
10
11
|
inventory?: InventoryPort;
|
|
11
12
|
resourceContext?: ResourceContextPort;
|
|
12
13
|
changeContext?: ChangeContextPort;
|
|
14
|
+
/** 밖에 물은 것이 실제로 있게 하는 통로 (C-04·C-10). 없으면 게시는 일어나지 않는다. */
|
|
15
|
+
coordinationSurface?: CoordinationSurfacePort;
|
|
13
16
|
/** 무엇을 왜 못 만들었는지. 조용히 빠지면 사람이 이유를 알 수 없다. */
|
|
14
17
|
unavailable: string[];
|
|
15
18
|
};
|
|
@@ -62,4 +65,37 @@ export declare function rolesFor(plan: BindingPlan, declared: readonly {
|
|
|
62
65
|
adapter: string;
|
|
63
66
|
resource: string;
|
|
64
67
|
}[]): Partial<Record<Capability, string>>;
|
|
68
|
+
/**
|
|
69
|
+
* 한 binding 이 여는 관측 통로 하나.
|
|
70
|
+
*
|
|
71
|
+
* **Monitor Core 는 이것을 여러 개 받는 것이 아니라, 하나씩 여러 번 받는다** — 채널마다
|
|
72
|
+
* 자기 cursor·coverage·observation ledger 를 갖는 별개의 Run 이다. 그래서 Core 에
|
|
73
|
+
* provider 분기도, multi-source 개념도 생기지 않는다 (설계 §8.1).
|
|
74
|
+
*/
|
|
75
|
+
export type ObservationChannel = {
|
|
76
|
+
/** Profile 이 선언한 역할. 선언이 없으면 발견된 자리라는 뜻으로 비어 있다. */
|
|
77
|
+
role?: string;
|
|
78
|
+
adapterId: string;
|
|
79
|
+
resource: string;
|
|
80
|
+
/** DEGRADED 도 채널이다 — 일부만 되는 것과 안 되는 것은 다르다. */
|
|
81
|
+
state: ResolvedBinding['state'];
|
|
82
|
+
detail?: string;
|
|
83
|
+
eventSource: EventSource;
|
|
84
|
+
inventory?: InventoryPort;
|
|
85
|
+
resourceContext?: ResourceContextPort;
|
|
86
|
+
changeContext?: ChangeContextPort;
|
|
87
|
+
};
|
|
88
|
+
export type ObservationChannels = {
|
|
89
|
+
channels: ObservationChannel[];
|
|
90
|
+
/** 무엇을 왜 못 열었는지. 조용히 빠지면 사람이 이유를 알 수 없다. */
|
|
91
|
+
unavailable: string[];
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* 선언된 binding 마다 관측 통로를 하나씩 연다.
|
|
95
|
+
*
|
|
96
|
+
* **선언이 있으면 선언만 본다.** 과거 mirror 로 남은 remote 가 발견됐다는 이유로 채널이
|
|
97
|
+
* 하나 더 생기면, 사람이 고르지 않은 곳을 감시하게 된다 — 발견은 후보이지 결합이 아니다
|
|
98
|
+
* (C-11 §7). 선언이 하나도 없으면 발견된 것을 쓰되, 그때는 갈리면 갈린다고 말한다.
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildObservationChannels(input: BuildInput): Promise<ObservationChannels>;
|
|
65
101
|
export declare function buildRuntimePorts(input: BuildInput): Promise<RuntimePorts>;
|
|
@@ -13,6 +13,7 @@ import { GitHubChangeContext, GitHubInventory, GitHubResourceContext } from "../
|
|
|
13
13
|
import { GitHubEventSource } from "../adapters/github/event-source.js";
|
|
14
14
|
import { GitHubScm } from "../adapters/github/scm.js";
|
|
15
15
|
import { GitLabClient, GlabApiClient, discoverToken as discoverGitLabToken, glabAvailable, } from "../adapters/gitlab/client.js";
|
|
16
|
+
import { GitLabCoordinationSurface } from "../adapters/gitlab/coordination.js";
|
|
16
17
|
import { GitLabChangeContext, GitLabEventSource, GitLabInventory, GitLabResourceContext, } from "../adapters/gitlab/ports.js";
|
|
17
18
|
import { JamMcpClient } from "../adapters/jam/mcp-client.js";
|
|
18
19
|
import { JamEventSource } from "../adapters/jam/event-source.js";
|
|
@@ -30,7 +31,10 @@ const FACTORIES = {
|
|
|
30
31
|
inventory: new GitLabInventory({ client, project }),
|
|
31
32
|
resourceContext: new GitLabResourceContext({ client, project }),
|
|
32
33
|
changeContext: new GitLabChangeContext({ client, project }),
|
|
33
|
-
//
|
|
34
|
+
// 조율 표면. 토큰 통로일 때만 쓰기가 가능하다 — 도구를 통로로 쓰는 경우도 POST 를
|
|
35
|
+
// 대신 보내 준다. 둘 다 아니면 create 가 그 사실을 그대로 말한다.
|
|
36
|
+
coordinationSurface: new GitLabCoordinationSurface({ reader: client, writer: client, project }),
|
|
37
|
+
// canonical 통로는 아직 없다. 없는 것을 있는 척하지 않는다.
|
|
34
38
|
};
|
|
35
39
|
},
|
|
36
40
|
github(binding, input, token) {
|
|
@@ -112,6 +116,7 @@ const PORT_OF = {
|
|
|
112
116
|
'context.change': 'changeContext',
|
|
113
117
|
'context.resource': 'resourceContext',
|
|
114
118
|
'canonical.read': 'scm',
|
|
119
|
+
'coordination.surface': 'coordinationSurface',
|
|
115
120
|
};
|
|
116
121
|
/**
|
|
117
122
|
* capability가 필요한 자리마다 어느 binding이 맡을지 정해 Port를 만든다.
|
|
@@ -140,6 +145,80 @@ export function rolesFor(plan, declared) {
|
|
|
140
145
|
}
|
|
141
146
|
return roles;
|
|
142
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 관측 capability — 이 셋은 **하나를 고르는 문제가 아니다** (설계 §8).
|
|
150
|
+
*
|
|
151
|
+
* 코드가 GitLab 에 있고 작업 항목이 Jira 에 있는 프로젝트에서 둘 다 봐야 한다는 것은
|
|
152
|
+
* 요구이지 모호함이 아니다. 그런데 지금까지는 같은 capability 를 둘이 제공한다는
|
|
153
|
+
* 이유만으로 AMBIGUOUS 가 되어 감시가 통째로 서지 않았다.
|
|
154
|
+
*
|
|
155
|
+
* 그래서 capability 를 두 부류로 가른다:
|
|
156
|
+
*
|
|
157
|
+
* singular canonical.read 처럼 **한 곳이어야** 의미가 서는 것 → 역할로 하나를 고른다
|
|
158
|
+
* observation observe.delta · inventory.enumerate · context.resource → binding 마다 하나씩
|
|
159
|
+
*/
|
|
160
|
+
const OBSERVATION_CAPABILITIES = [
|
|
161
|
+
'observe.delta',
|
|
162
|
+
'inventory.enumerate',
|
|
163
|
+
'context.resource',
|
|
164
|
+
];
|
|
165
|
+
/**
|
|
166
|
+
* 선언된 binding 마다 관측 통로를 하나씩 연다.
|
|
167
|
+
*
|
|
168
|
+
* **선언이 있으면 선언만 본다.** 과거 mirror 로 남은 remote 가 발견됐다는 이유로 채널이
|
|
169
|
+
* 하나 더 생기면, 사람이 고르지 않은 곳을 감시하게 된다 — 발견은 후보이지 결합이 아니다
|
|
170
|
+
* (C-11 §7). 선언이 하나도 없으면 발견된 것을 쓰되, 그때는 갈리면 갈린다고 말한다.
|
|
171
|
+
*/
|
|
172
|
+
export async function buildObservationChannels(input) {
|
|
173
|
+
const usable = input.plan.bindings.filter((binding) => (binding.state === 'AVAILABLE' || binding.state === 'DEGRADED') &&
|
|
174
|
+
OBSERVATION_CAPABILITIES.some((capability) => binding.provides.includes(capability)));
|
|
175
|
+
const declared = usable.filter((binding) => binding.role !== undefined);
|
|
176
|
+
const chosen = declared.length > 0 ? declared : usable;
|
|
177
|
+
const channels = [];
|
|
178
|
+
const unavailable = [];
|
|
179
|
+
if (chosen.length === 0) {
|
|
180
|
+
const blocked = input.plan.bindings.filter((binding) => OBSERVATION_CAPABILITIES.some((capability) => binding.provides.includes(capability)));
|
|
181
|
+
unavailable.push(blocked.length > 0
|
|
182
|
+
? `관측 통로를 제공하는 binding 이 있으나 지금 쓸 수 없다 (${blocked
|
|
183
|
+
.map((binding) => `${binding.adapterId}: ${binding.state}`)
|
|
184
|
+
.join(', ')})`
|
|
185
|
+
: '관측 통로를 제공하는 binding 이 없다');
|
|
186
|
+
return { channels, unavailable };
|
|
187
|
+
}
|
|
188
|
+
const findToken = input.findToken ??
|
|
189
|
+
(async (adapterId) => adapterId === 'gitlab' ? await discoverGitLabAccess() : await discoverToken());
|
|
190
|
+
for (const binding of chosen) {
|
|
191
|
+
const where = `${binding.adapterId}:${binding.resource}`;
|
|
192
|
+
const factory = FACTORIES[binding.adapterId];
|
|
193
|
+
if (!factory) {
|
|
194
|
+
unavailable.push(`${where}: 이 빌드에 조립 경로가 없다`);
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
const token = TOKENLESS.has(binding.adapterId) ? '' : await findToken(binding.adapterId);
|
|
198
|
+
if (token === null) {
|
|
199
|
+
// 자격이 없는 것은 "변화 없음"이 아니다 — 그 채널만 빠지고 이유가 남는다
|
|
200
|
+
unavailable.push(`${where}: 자격이 없어 관측 통로를 만들지 않았다`);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const made = factory(binding, input, token);
|
|
204
|
+
if (!made.eventSource) {
|
|
205
|
+
unavailable.push(`${where}: ${binding.adapterId} 가 관측 통로를 만들지 않았다`);
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
channels.push({
|
|
209
|
+
...(binding.role ? { role: binding.role } : {}),
|
|
210
|
+
adapterId: binding.adapterId,
|
|
211
|
+
resource: binding.resource,
|
|
212
|
+
state: binding.state,
|
|
213
|
+
...(binding.detail ? { detail: binding.detail } : {}),
|
|
214
|
+
eventSource: made.eventSource,
|
|
215
|
+
...(made.inventory ? { inventory: made.inventory } : {}),
|
|
216
|
+
...(made.resourceContext ? { resourceContext: made.resourceContext } : {}),
|
|
217
|
+
...(made.changeContext ? { changeContext: made.changeContext } : {}),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
return { channels, unavailable };
|
|
221
|
+
}
|
|
143
222
|
export async function buildRuntimePorts(input) {
|
|
144
223
|
const ports = { unavailable: [] };
|
|
145
224
|
// 자격은 adapter마다 다른 곳에 있다. Core는 이 사실을 모르고, 여기서만 안다.
|
|
@@ -18,7 +18,7 @@ export type SetupState = {
|
|
|
18
18
|
* runtime 디렉터리는 있는데 profile.lock을 읽지 못하는 상태 — 붙이다 만 것이다.
|
|
19
19
|
*
|
|
20
20
|
* 이것을 "붙어 있음"으로 읽으면 plan은 `applied`를 답하면서 실패할 `asc proceed`를
|
|
21
|
-
* 다음 행동으로 준다 (
|
|
21
|
+
* 다음 행동으로 준다 (Windows 실전 실측 ASC-2: 파일 잠금이 빈 skeleton만 남긴
|
|
22
22
|
* 경우). 붙이다 만 상태는 붙일 것이 남은 상태다 — repair가 plan에 드러나야 한다.
|
|
23
23
|
*/
|
|
24
24
|
attachmentBroken?: boolean;
|
|
@@ -42,6 +42,36 @@ export type SetupState = {
|
|
|
42
42
|
* 그때는 설치된 `asc` 를 전제하지 않는다.
|
|
43
43
|
*/
|
|
44
44
|
stableRuntime?: StableInstallState;
|
|
45
|
+
/**
|
|
46
|
+
* 이 기계의 지속 등록 상태 (설계 §6, Gate 7).
|
|
47
|
+
*
|
|
48
|
+
* **별도 onboarding 을 만들지 않는다.** 사용자가 `runtime enable` 을 따로 치게 하면
|
|
49
|
+
* 그것이 곧 "켜는 행위"가 되고, 이 제품이 없애려던 바로 그 단계다. 같은 plan 이
|
|
50
|
+
* runtime·host·workspace 와 함께 판단한다.
|
|
51
|
+
*/
|
|
52
|
+
persistentRuntime?: {
|
|
53
|
+
action: 'none' | 'install' | 'unsupported';
|
|
54
|
+
adapter: string;
|
|
55
|
+
detail?: string;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Profile 이 선언한 작업 항목 결합의 준비 상태 (설계 §9.3).
|
|
59
|
+
*
|
|
60
|
+
* **선언은 이미 내려진 결정이다.** 그런데 그 도구가 준비되지 않았다는 이유로 setup 이
|
|
61
|
+
* 사람에게 "그 도구를 설정할까요?"라고 되물으면, 사람은 자기가 이미 적어 둔 것을 다시
|
|
62
|
+
* 답하게 된다. 고칠 수 있는 것은 고치고, 사람만 할 수 있는 것에서만 멈춘다.
|
|
63
|
+
*/
|
|
64
|
+
workBinding?: {
|
|
65
|
+
adapter: string;
|
|
66
|
+
resource: string;
|
|
67
|
+
/** 지금 쓸 수 있는가. 쓸 수 있으면 아래 값들은 보지 않는다. */
|
|
68
|
+
ready: boolean;
|
|
69
|
+
/** 고칠 수 있는가, 사람이 해야 하는가, 다시 돌려도 소용없는가. */
|
|
70
|
+
remedy?: 'SELF_HEAL' | 'HUMAN' | 'HARD';
|
|
71
|
+
detail?: string;
|
|
72
|
+
/** 그 도구가 말한 자기 버전. 없으면 부를 수 없다 — 버전을 지어내지 않는다. */
|
|
73
|
+
version?: string;
|
|
74
|
+
};
|
|
45
75
|
};
|
|
46
76
|
export type SetupChange =
|
|
47
77
|
/**
|
|
@@ -67,6 +97,27 @@ export type SetupChange =
|
|
|
67
97
|
target: 'host-install';
|
|
68
98
|
host: string;
|
|
69
99
|
from: string;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Profile 이 선언한 작업 도구를 **그 도구의 공식 setup 으로** 되살린다 (설계 §9.3).
|
|
103
|
+
*
|
|
104
|
+
* ASC 가 그 도구의 설정을 손으로 조립하지 않는다 — 부르기만 한다.
|
|
105
|
+
*/
|
|
106
|
+
| {
|
|
107
|
+
target: 'work-binding-setup';
|
|
108
|
+
adapter: string;
|
|
109
|
+
resource: string;
|
|
110
|
+
version: string;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 이 기계에 ASC runtime 을 등록한다 (설계 §4).
|
|
114
|
+
*
|
|
115
|
+
* **workspace 마다가 아니라 기계당 하나다.** 그래서 이 변경은 프로젝트와 무관하고,
|
|
116
|
+
* 붙는 것과 같은 계획에 함께 실린다.
|
|
117
|
+
*/
|
|
118
|
+
| {
|
|
119
|
+
target: 'persistent-runtime';
|
|
120
|
+
adapter: string;
|
|
70
121
|
};
|
|
71
122
|
export type SetupStatus = 'already_configured' | 'ready_to_apply' | 'user_action_required';
|
|
72
123
|
export type SetupCode =
|
|
@@ -75,7 +126,12 @@ export type SetupCode =
|
|
|
75
126
|
/** 저장소에 두는 것은 팀의 결정이다 (C-11 불변식 ⑤). */
|
|
76
127
|
| 'ASC_PROJECT_SCOPE_REQUIRES_CONSENT'
|
|
77
128
|
/** 설치물을 사람이 고쳤다 — 덮는 것은 사람이 정한다 (L-5). */
|
|
78
|
-
| 'ASC_HOST_INSTALL_MODIFIED'
|
|
129
|
+
| 'ASC_HOST_INSTALL_MODIFIED'
|
|
130
|
+
/**
|
|
131
|
+
* 작업 도구가 사람을 기다린다 — 자격 입력처럼 ASC 가 대신할 수 없는 것 (설계 §9.4).
|
|
132
|
+
* ASC 는 토큰을 받지도 저장하지도 않는다.
|
|
133
|
+
*/
|
|
134
|
+
| 'ASC_WORK_BINDING_NEEDS_USER';
|
|
79
135
|
/**
|
|
80
136
|
* 다음에 할 일 하나. **두 형태를 함께 든다** (C-14 §3.4, 불변식 ⑯).
|
|
81
137
|
*
|
|
@@ -126,6 +182,14 @@ export type SetupEffects = {
|
|
|
126
182
|
installHost(change: Extract<SetupChange, {
|
|
127
183
|
target: 'host-install';
|
|
128
184
|
}>): Promise<void>;
|
|
185
|
+
/** 작업 도구의 공식 setup 을 부른다. ASC 가 그 설정을 조립하지 않는다. */
|
|
186
|
+
setupWorkBinding?(change: Extract<SetupChange, {
|
|
187
|
+
target: 'work-binding-setup';
|
|
188
|
+
}>): Promise<void>;
|
|
189
|
+
/** 이 기계에 runtime 을 등록한다. OS 별 형식은 adapter 뒤에 있다. */
|
|
190
|
+
registerPersistentRuntime?(change: Extract<SetupChange, {
|
|
191
|
+
target: 'persistent-runtime';
|
|
192
|
+
}>): Promise<void>;
|
|
129
193
|
};
|
|
130
194
|
export type ApplyResult = {
|
|
131
195
|
applied: SetupChange[];
|
|
@@ -78,6 +78,39 @@ export function computeSetupPlan(state) {
|
|
|
78
78
|
changes.push({ target: 'host-install', host: host.id, from: host.status });
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
+
// 이 기계의 지속 등록. 프로젝트와 무관하므로 profile 선택을 기다리지 않는다 —
|
|
82
|
+
// stable runtime 설치와 같은 자리다.
|
|
83
|
+
if (state.persistentRuntime) {
|
|
84
|
+
const persistent = state.persistentRuntime;
|
|
85
|
+
evidence.push(`persistent=${persistent.action} (${persistent.adapter})`);
|
|
86
|
+
// 쓸 수 없는 OS 에서는 계획에 담지 않는다. 못 하는 것을 "할 일"로 적지 않는다.
|
|
87
|
+
if (persistent.action === 'install') {
|
|
88
|
+
changes.push({ target: 'persistent-runtime', adapter: persistent.adapter });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Profile 이 선언한 작업 도구. **다시 묻지 않는다** — 결정은 이미 Profile 에 있다.
|
|
92
|
+
if (state.workBinding && !state.workBinding.ready) {
|
|
93
|
+
const work = state.workBinding;
|
|
94
|
+
evidence.push(`work:${work.adapter}=${work.remedy ?? 'NOT_READY'}`);
|
|
95
|
+
if (work.remedy === 'HUMAN') {
|
|
96
|
+
return {
|
|
97
|
+
status: 'user_action_required',
|
|
98
|
+
code: 'ASC_WORK_BINDING_NEEDS_USER',
|
|
99
|
+
changes,
|
|
100
|
+
requiresUserAction: true,
|
|
101
|
+
...actions(mode, evidence, [{ type: 'proceed', ...command(['setup', 'status']) }]),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
// 고칠 수 있는 것만 계획에 담는다. HARD 는 담지 않는다 — 다시 돌려도 달라지지 않는다.
|
|
105
|
+
if (work.remedy === 'SELF_HEAL' && work.version) {
|
|
106
|
+
changes.push({
|
|
107
|
+
target: 'work-binding-setup',
|
|
108
|
+
adapter: work.adapter,
|
|
109
|
+
resource: work.resource,
|
|
110
|
+
version: work.version,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
81
114
|
if (state.ascRoot && !state.attachmentBroken) {
|
|
82
115
|
// 붙어 있어도 **무엇을 고를 수 있었는지**는 사실이다. 사용자 소유 Profile을 새로 놓고
|
|
83
116
|
// 계획을 물었을 때 그것이 어디에도 안 보이면, 놓은 사람은 경로를 의심하게 된다.
|
|
@@ -174,6 +207,19 @@ export async function applySetupPlan(plan, effects) {
|
|
|
174
207
|
case 'host-install':
|
|
175
208
|
await effects.installHost(change);
|
|
176
209
|
break;
|
|
210
|
+
case 'persistent-runtime':
|
|
211
|
+
// 이 갈래를 모르는 호출자에게는 이 변경이 없던 것으로 남는다.
|
|
212
|
+
if (!effects.registerPersistentRuntime)
|
|
213
|
+
continue;
|
|
214
|
+
await effects.registerPersistentRuntime(change);
|
|
215
|
+
break;
|
|
216
|
+
case 'work-binding-setup':
|
|
217
|
+
// 이 갈래를 모르는 호출자에게는 이 변경이 없던 것으로 남는다 —
|
|
218
|
+
// 안 한 것을 "했다"로 적지 않는다.
|
|
219
|
+
if (!effects.setupWorkBinding)
|
|
220
|
+
continue;
|
|
221
|
+
await effects.setupWorkBinding(change);
|
|
222
|
+
break;
|
|
177
223
|
}
|
|
178
224
|
applied.push(change);
|
|
179
225
|
}
|
|
@@ -204,5 +250,9 @@ function changeLine(change) {
|
|
|
204
250
|
return ` attach: ${change.profile} · scope ${change.scope}`;
|
|
205
251
|
case 'host-install':
|
|
206
252
|
return ` converge host installation: ${change.host} (currently ${change.from})`;
|
|
253
|
+
case 'work-binding-setup':
|
|
254
|
+
return ` repair ${change.adapter} for ${change.resource} through its own setup (${change.version})`;
|
|
255
|
+
case 'persistent-runtime':
|
|
256
|
+
return ` register this machine's ASC runtime with ${change.adapter}`;
|
|
207
257
|
}
|
|
208
258
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 여기에 provider 이름을 넣지 않는다 — `scm.<제품>` 같은 값은 capability가 아니라
|
|
5
5
|
* adapter identity이며, 그것으로 갈라지기 시작하면 Core가 provider를 아는 셈이 된다.
|
|
6
6
|
*/
|
|
7
|
-
export declare const CAPABILITIES: readonly ["observe.delta", "inventory.enumerate", "context.resource", "context.thread", "context.change", "context.history", "canonical.read", "action.comment", "action.update", "presentation.digest", "presentation.priority", "approval.interactive", "identity.resolve"];
|
|
7
|
+
export declare const CAPABILITIES: readonly ["observe.delta", "inventory.enumerate", "context.resource", "context.thread", "context.change", "context.history", "canonical.read", "action.comment", "coordination.surface", "action.update", "presentation.digest", "presentation.priority", "approval.interactive", "identity.resolve"];
|
|
8
8
|
export type Capability = (typeof CAPABILITIES)[number];
|
|
9
9
|
/** describe() 산출물 — 호출도 네트워크도 없는 정적 선언 (C-09 §5). */
|
|
10
10
|
export type AdapterDescriptor = {
|
|
@@ -20,3 +20,29 @@ export type ResolveDeps = {
|
|
|
20
20
|
*/
|
|
21
21
|
export declare function shimTarget(shimText: string): string | null;
|
|
22
22
|
export declare function resolveExternalCommand(command: string, args: readonly string[], deps?: ResolveDeps): ResolvedInvocation;
|
|
23
|
+
/**
|
|
24
|
+
* PATH 에서 실행 파일 하나를 찾는다 (POSIX). 없으면 null — 있는 척하지 않는다.
|
|
25
|
+
*
|
|
26
|
+
* Windows 는 위 `resolveExternalCommand` 가 shim 까지 풀어 주므로 여기서는 다루지 않는다.
|
|
27
|
+
*/
|
|
28
|
+
export declare function findOnPath(command: string, deps?: {
|
|
29
|
+
env?: NodeJS.ProcessEnv;
|
|
30
|
+
exists?: (path: string) => boolean;
|
|
31
|
+
platform?: NodeJS.Platform;
|
|
32
|
+
}): string | null;
|
|
33
|
+
/** 시스템 기본 자리. 서비스 관리자가 주는 PATH 가 대개 이것이다. */
|
|
34
|
+
export declare const SYSTEM_PATH: readonly ["/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"];
|
|
35
|
+
/**
|
|
36
|
+
* 서비스에 실어 보낼 PATH — **지금 이 셸의 PATH 를 통째로 옮기지 않는다.** 세션마다 붙는
|
|
37
|
+
* 임시 디렉터리가 그대로 들어가면 등록물이 매번 STALE 이 되고, 사라진 경로가 남는다.
|
|
38
|
+
* 대신 필요한 실행 파일이 **실제로 있는 디렉터리만** 고른다. 못 찾은 도구는 조용히 빠지지
|
|
39
|
+
* 않고 `missing` 에 남는다 — 그 도구를 쓰는 통로는 서비스에서도 열리지 않을 것이다.
|
|
40
|
+
*/
|
|
41
|
+
export declare function servicePath(tools: readonly string[], deps?: {
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
exists?: (path: string) => boolean;
|
|
44
|
+
platform?: NodeJS.Platform;
|
|
45
|
+
}): {
|
|
46
|
+
path: string;
|
|
47
|
+
missing: string[];
|
|
48
|
+
};
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Node는 보안 수정 이후 shell 없이 `.cmd` 를 실행하지 않는다. 그런데 npm이 전역 설치로
|
|
4
4
|
// 만들어 주는 명령은 Windows에서 전부 `.cmd` shim이다 — bare 이름을 Unix 방식으로만
|
|
5
5
|
// spawn하면 ENOENT/EINVAL이 나고, 호출자는 "설치돼 있지 않다"고 오판한다
|
|
6
|
-
// (
|
|
6
|
+
// (Windows 실전 실측: shim이 PATH에 실재하는데 host probe가 not found →
|
|
7
7
|
// external_write_guard STOP까지 이어졌다).
|
|
8
8
|
//
|
|
9
9
|
// shell을 켜는 것은 답이 아니다 — 인자가 escape 없이 이어붙는다(DEP0190). 대신:
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// ③ shim을 못 읽으면 cmd.exe /d /c 로 그 .cmd 를 부른다 — cmd.exe는 진짜 실행 파일이라
|
|
14
14
|
// shell 옵션이 필요 없다.
|
|
15
15
|
// 셋 다 실패하면 이름 그대로 돌려준다 — PATH에 진짜 실행 파일이 있는 환경이 그 경우다.
|
|
16
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
16
|
+
import { accessSync, constants, existsSync, readFileSync, statSync } from 'node:fs';
|
|
17
17
|
import { delimiter as winDelimiter, dirname, extname, isAbsolute, join } from 'node:path/win32';
|
|
18
18
|
const defaultRead = (path) => {
|
|
19
19
|
try {
|
|
@@ -77,3 +77,60 @@ export function resolveExternalCommand(command, args, deps = {}) {
|
|
|
77
77
|
}
|
|
78
78
|
return { command, args: [...args] };
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* PATH 에서 실행 파일 하나를 찾는다 (POSIX). 없으면 null — 있는 척하지 않는다.
|
|
82
|
+
*
|
|
83
|
+
* Windows 는 위 `resolveExternalCommand` 가 shim 까지 풀어 주므로 여기서는 다루지 않는다.
|
|
84
|
+
*/
|
|
85
|
+
export function findOnPath(command, deps = {}) {
|
|
86
|
+
const platform = deps.platform ?? process.platform;
|
|
87
|
+
if (platform === 'win32')
|
|
88
|
+
return null;
|
|
89
|
+
if (command.includes('/'))
|
|
90
|
+
return (deps.exists ?? isExecutable)(command) ? command : null;
|
|
91
|
+
const exists = deps.exists ?? isExecutable;
|
|
92
|
+
const pathValue = (deps.env ?? process.env).PATH ?? '';
|
|
93
|
+
for (const dir of pathValue.split(':')) {
|
|
94
|
+
if (!dir)
|
|
95
|
+
continue;
|
|
96
|
+
const candidate = `${dir}/${command}`;
|
|
97
|
+
if (exists(candidate))
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const isExecutable = (path) => {
|
|
103
|
+
try {
|
|
104
|
+
accessSync(path, constants.X_OK);
|
|
105
|
+
return statSync(path).isFile();
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/** 시스템 기본 자리. 서비스 관리자가 주는 PATH 가 대개 이것이다. */
|
|
112
|
+
export const SYSTEM_PATH = ['/usr/local/bin', '/usr/bin', '/bin', '/usr/sbin', '/sbin'];
|
|
113
|
+
/**
|
|
114
|
+
* 서비스에 실어 보낼 PATH — **지금 이 셸의 PATH 를 통째로 옮기지 않는다.** 세션마다 붙는
|
|
115
|
+
* 임시 디렉터리가 그대로 들어가면 등록물이 매번 STALE 이 되고, 사라진 경로가 남는다.
|
|
116
|
+
* 대신 필요한 실행 파일이 **실제로 있는 디렉터리만** 고른다. 못 찾은 도구는 조용히 빠지지
|
|
117
|
+
* 않고 `missing` 에 남는다 — 그 도구를 쓰는 통로는 서비스에서도 열리지 않을 것이다.
|
|
118
|
+
*/
|
|
119
|
+
export function servicePath(tools, deps = {}) {
|
|
120
|
+
const dirs = [];
|
|
121
|
+
const missing = [];
|
|
122
|
+
const push = (dir) => {
|
|
123
|
+
if (!dirs.includes(dir))
|
|
124
|
+
dirs.push(dir);
|
|
125
|
+
};
|
|
126
|
+
for (const tool of tools) {
|
|
127
|
+
const found = findOnPath(tool, deps);
|
|
128
|
+
if (found)
|
|
129
|
+
push(found.slice(0, found.lastIndexOf('/')) || '/');
|
|
130
|
+
else
|
|
131
|
+
missing.push(tool);
|
|
132
|
+
}
|
|
133
|
+
for (const dir of SYSTEM_PATH)
|
|
134
|
+
push(dir);
|
|
135
|
+
return { path: dirs.join(':'), missing };
|
|
136
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** 이 기계에서 ASC 가 소유하는 등록물 하나. 이름이 곧 소유권 증거다. */
|
|
2
|
+
export declare const SERVICE_LABEL = "com.asc-agent.runtime";
|
|
3
|
+
/**
|
|
4
|
+
* 등록물이 실행할 명령.
|
|
5
|
+
*
|
|
6
|
+
* **한 회차만 돈다** (`runtime tick`). 계속 도는 프로세스를 등록하지 않는 이유는 셋이다:
|
|
7
|
+
* OS 가 이미 주기를 관리할 줄 알고, 죽었을 때 되살리는 것도 OS 가 더 잘하며, 짧게 도는
|
|
8
|
+
* 프로세스는 죽어 있는 동안 자원을 쓰지 않는다.
|
|
9
|
+
*/
|
|
10
|
+
export type ServiceCommand = {
|
|
11
|
+
/** 실행 파일. 보통 지금 도는 node. */
|
|
12
|
+
program: string;
|
|
13
|
+
/** 인자. 첫 항목이 ASC 진입점이다. */
|
|
14
|
+
args: readonly string[];
|
|
15
|
+
/** 회차 간격(초). Core 상수가 아니다 — 호출자가 정한다 (C-12 불변식 ③). */
|
|
16
|
+
intervalSeconds: number;
|
|
17
|
+
/**
|
|
18
|
+
* 등록물이 돌 때의 환경. **서비스는 로그인 셸의 PATH 를 물려받지 않는다** — 실기계에서
|
|
19
|
+
* launchd 가 준 PATH 는 `/usr/bin:/bin:/usr/sbin:/sbin` 뿐이라 외부 통로 실행 파일을
|
|
20
|
+
* 하나도 못 찾았다. 등록하는 쪽이 필요한 것을 결정해 여기 실어 보내고, 내용이 바뀌면
|
|
21
|
+
* 등록물이 STALE 로 드러난다 — 셸 설정을 추측하는 경로는 없다.
|
|
22
|
+
*/
|
|
23
|
+
environment?: Readonly<Record<string, string>>;
|
|
24
|
+
/** 회차의 표준 출력·오류를 남길 파일. 없으면 OS 기본(대개 버려진다). */
|
|
25
|
+
logPath?: string;
|
|
26
|
+
};
|
|
27
|
+
/** 지금 이 기계의 등록 상태. */
|
|
28
|
+
export type ServiceState =
|
|
29
|
+
/** 등록된 적이 없다. */
|
|
30
|
+
{
|
|
31
|
+
kind: 'ABSENT';
|
|
32
|
+
}
|
|
33
|
+
/** 지금 우리가 쓰려는 것과 같다. */
|
|
34
|
+
| {
|
|
35
|
+
kind: 'CURRENT';
|
|
36
|
+
detail?: string;
|
|
37
|
+
}
|
|
38
|
+
/** 우리 것인데 낡았다 — 경로나 간격이 달라졌다. 수렴시킨다. */
|
|
39
|
+
| {
|
|
40
|
+
kind: 'STALE';
|
|
41
|
+
detail: string;
|
|
42
|
+
}
|
|
43
|
+
/** 이 OS 에서는 등록할 방법을 모른다. 없는 것을 있는 척하지 않는다. */
|
|
44
|
+
| {
|
|
45
|
+
kind: 'UNSUPPORTED';
|
|
46
|
+
detail: string;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* OS 하나가 지켜야 할 계약.
|
|
50
|
+
*
|
|
51
|
+
* `uninstall` 은 **ASC 가 소유한 등록물만** 지운다. 사람이 만든 같은 이름의 무언가가
|
|
52
|
+
* 있으면 그것은 사람의 것이다 (C-03 §5.1 의 소유권 규칙과 같은 선).
|
|
53
|
+
*/
|
|
54
|
+
export type PersistentRuntimeAdapter = {
|
|
55
|
+
id: 'launchd' | 'schtasks' | 'systemd-user';
|
|
56
|
+
/** 이 기계에서 쓸 수 있는가. 쓸 수 없으면 install 을 시도하지 않는다. */
|
|
57
|
+
supported(): Promise<boolean>;
|
|
58
|
+
status(command: ServiceCommand): Promise<ServiceState>;
|
|
59
|
+
install(command: ServiceCommand): Promise<void>;
|
|
60
|
+
uninstall(): Promise<void>;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* 무엇을 해야 하는가 — **아무것도 하지 않는다.**
|
|
64
|
+
*
|
|
65
|
+
* setup 의 detect→plan→apply 와 같은 모양이다: 판정과 실행을 나누고, 계획에 없는 것은
|
|
66
|
+
* 일어나지 않는다 (C-14 불변식 ⑩).
|
|
67
|
+
*/
|
|
68
|
+
export type PersistentRuntimePlan = {
|
|
69
|
+
action: 'none';
|
|
70
|
+
state: ServiceState;
|
|
71
|
+
} | {
|
|
72
|
+
action: 'install';
|
|
73
|
+
state: ServiceState;
|
|
74
|
+
} | {
|
|
75
|
+
action: 'unsupported';
|
|
76
|
+
state: Extract<ServiceState, {
|
|
77
|
+
kind: 'UNSUPPORTED';
|
|
78
|
+
}>;
|
|
79
|
+
};
|
|
80
|
+
export declare function planPersistentRuntime(adapter: PersistentRuntimeAdapter, command: ServiceCommand): Promise<PersistentRuntimePlan>;
|
|
81
|
+
/** 사람이 읽는 한 줄. 왜 그 판정인지가 함께 와야 한다. */
|
|
82
|
+
export declare function persistentRuntimeLine(id: string, plan: PersistentRuntimePlan): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Persistent Runtime Port — 사용자가 켜지 않아도 도는 자리 (설계 §4·§5).
|
|
2
|
+
//
|
|
3
|
+
// C-12 는 상시성을 "상태를 지속시키고 계산을 짧게 돌리는 것"으로 정의했고, 불변식 ④ 는
|
|
4
|
+
// **Core 에 scheduler 제품을 박지 말라**고 했다. 그 둘을 함께 지키는 방법은 하나다:
|
|
5
|
+
// Core 는 "무엇을 등록해야 하는가"만 말하고, launchd·Task Scheduler·systemd 는 그 말을
|
|
6
|
+
// 자기 형식으로 옮기는 adapter 뒤에 둔다.
|
|
7
|
+
//
|
|
8
|
+
// Core 이 기계에 ASC runtime 하나가 등록돼 있어야 한다
|
|
9
|
+
// Adapter 그것을 이 OS 에서 어떻게 표현하는가
|
|
10
|
+
//
|
|
11
|
+
// **workspace 마다 하나가 아니다.** 사용자/기계당 하나이고, 그 하나가 여러 workspace 를
|
|
12
|
+
// 돌본다 (설계 §4.1) — workspace 가 늘 때마다 OS 서비스가 늘면 그것은 제품이 아니라 짐이다.
|
|
13
|
+
//
|
|
14
|
+
// root daemon 으로 올리지 않는다. 사용자 로그인 이후가 이 계약의 경계다 (설계 §4.3).
|
|
15
|
+
/** 이 기계에서 ASC 가 소유하는 등록물 하나. 이름이 곧 소유권 증거다. */
|
|
16
|
+
export const SERVICE_LABEL = 'com.asc-agent.runtime';
|
|
17
|
+
export async function planPersistentRuntime(adapter, command) {
|
|
18
|
+
if (!(await adapter.supported())) {
|
|
19
|
+
return {
|
|
20
|
+
action: 'unsupported',
|
|
21
|
+
state: { kind: 'UNSUPPORTED', detail: `${adapter.id} is not usable on this machine` },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const state = await adapter.status(command);
|
|
25
|
+
switch (state.kind) {
|
|
26
|
+
case 'CURRENT':
|
|
27
|
+
return { action: 'none', state };
|
|
28
|
+
case 'UNSUPPORTED':
|
|
29
|
+
return { action: 'unsupported', state };
|
|
30
|
+
// 없는 것과 낡은 것은 다른 사실이지만 할 일은 같다 — 지금 형태로 수렴시킨다.
|
|
31
|
+
case 'ABSENT':
|
|
32
|
+
case 'STALE':
|
|
33
|
+
return { action: 'install', state };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** 사람이 읽는 한 줄. 왜 그 판정인지가 함께 와야 한다. */
|
|
37
|
+
export function persistentRuntimeLine(id, plan) {
|
|
38
|
+
switch (plan.action) {
|
|
39
|
+
case 'none':
|
|
40
|
+
return `Persistent runtime: registered with ${id}${plan.state.kind === 'CURRENT' && plan.state.detail ? ` (${plan.state.detail})` : ''}`;
|
|
41
|
+
case 'install':
|
|
42
|
+
return plan.state.kind === 'STALE'
|
|
43
|
+
? `Persistent runtime: registration is behind — ${plan.state.detail}`
|
|
44
|
+
: `Persistent runtime: not registered yet`;
|
|
45
|
+
case 'unsupported':
|
|
46
|
+
// 못 하는 것을 "안 해도 된다"로 적지 않는다
|
|
47
|
+
return `Persistent runtime: this machine has no user-scope service manager ASC knows — ${plan.state.detail}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export declare const RUNTIME_PACKAGE = "@asc-agent/runtime";
|
|
2
2
|
export declare const BOOTSTRAP_PACKAGE = "@asc-agent/bootstrap";
|
|
3
3
|
/** runtime과 bootstrap은 초기 release에서 lockstep이다. */
|
|
4
|
-
export declare const RELEASE_VERSION = "0.
|
|
5
|
-
export declare const RUNTIME_SPEC = "@asc-agent/runtime@0.
|
|
6
|
-
export declare const BOOTSTRAP_SPEC = "@asc-agent/bootstrap@0.
|
|
4
|
+
export declare const RELEASE_VERSION = "0.5.0";
|
|
5
|
+
export declare const RUNTIME_SPEC = "@asc-agent/runtime@0.5.0";
|
|
6
|
+
export declare const BOOTSTRAP_SPEC = "@asc-agent/bootstrap@0.5.0";
|
|
7
7
|
/**
|
|
8
8
|
* 아직 설치되지 않은 machine에서 그대로 실행되는 형태 (C-14 §3.4).
|
|
9
9
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const RUNTIME_PACKAGE = '@asc-agent/runtime';
|
|
10
10
|
export const BOOTSTRAP_PACKAGE = '@asc-agent/bootstrap';
|
|
11
11
|
/** runtime과 bootstrap은 초기 release에서 lockstep이다. */
|
|
12
|
-
export const RELEASE_VERSION = '0.
|
|
12
|
+
export const RELEASE_VERSION = '0.5.0';
|
|
13
13
|
export const RUNTIME_SPEC = `${RUNTIME_PACKAGE}@${RELEASE_VERSION}`;
|
|
14
14
|
export const BOOTSTRAP_SPEC = `${BOOTSTRAP_PACKAGE}@${RELEASE_VERSION}`;
|
|
15
15
|
/**
|