openyida 2026.10.14 → 2026.10.15
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 +10 -20
- package/bin/yida.js +5 -0
- package/lib/agent/frozen-bundle.js +21 -11
- package/lib/agent/private-directory.js +39 -19
- package/lib/agent/provider-preflight.js +3 -7
- package/lib/agent/runtime-catalog.json +12 -12
- package/lib/agent/runtime-package.js +7 -3
- package/lib/agent/stdio.js +9 -4
- package/lib/app/app-entry.js +16 -3
- package/lib/app/update-form-config.js +5 -2
- package/lib/core/agent-capabilities.js +1 -0
- package/lib/core/command-manifest.js +59 -12
- package/lib/core/locales/en.js +16 -1
- package/lib/core/locales/zh.js +16 -1
- package/lib/core/theme-widget.js +14 -1
- package/lib/design-plan/confirmation.js +23 -2
- package/lib/design-plan/entry-navigation.js +3 -1
- package/lib/design-plan/init.js +2 -1
- package/lib/design-plan/materialize.js +1 -1
- package/lib/design-plan/patch.js +1 -1
- package/lib/design-plan/visual-policy.js +26 -8
- package/lib/style/data-manage-presets.json +146 -0
- package/lib/style/data-manage-style.js +141 -0
- package/lib/style/native-data-manage.js +300 -0
- package/package.json +7 -6
- package/yida-skills/SKILL.md +2 -1
- package/yida-skills/references/field-and-url-reference.md +9 -8
- package/yida-skills/skills/yida-app/SKILL.md +7 -1
- package/yida-skills/skills/yida-app/references/entry-navigation.md +31 -5
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +13 -25
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +4 -4
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +6 -1
- package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +6 -1
- package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +14 -6
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/references/navigation-and-entry-guide.md +3 -1
- package/yida-skills/skills/yida-data-manage-ui/SKILL.md +70 -0
- package/yida-skills/skills/yida-data-manage-ui/references/column-emphasis.md +20 -0
- package/yida-skills/skills/yida-data-manage-ui/references/custom-layouts.md +23 -0
- package/yida-skills/skills/yida-data-manage-ui/references/style-decisions.md +32 -0
- package/yida-skills/skills/yida-data-manage-ui/references/token-and-css.md +77 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +2 -0
- package/yida-skills/skills/yida-design/SKILL.md +7 -3
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +32 -4
- package/yida-skills/skills/yida-design/references/reference-visual-dna.md +34 -0
- package/yida-skills/skills/yida-design/references/theme-selection.md +35 -18
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/SKILL.md +2 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/confirmation.md +1 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +5 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +7 -8
- package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +1 -1
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +2 -0
- package/yida-skills/skills/yida-nav-shell/SKILL.md +2 -2
- package/yida-skills/skills/yida-page-config/SKILL.md +2 -2
- package/yida-skills/skills/yida-publish-page/SKILL.md +1 -1
- package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +4 -2
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +1 -1
- package/yida-skills/skills-index.json +42 -0
package/README.md
CHANGED
|
@@ -47,7 +47,7 @@ Example applications built with the themes below. Click an image to view it at f
|
|
|
47
47
|
</tr>
|
|
48
48
|
</table>
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Use an **application theme or an independent creative direction**. Describe a style you like, or provide a reference website or design image to customize your app’s appearance. [Browse more application examples](https://demo.aliwork.com/o/openyida/helpCenter?openyidaRoute=showcase).
|
|
51
51
|
|
|
52
52
|
## Core Capabilities
|
|
53
53
|
|
|
@@ -64,7 +64,7 @@ Choose an **application theme or an independent creative direction**. The agent
|
|
|
64
64
|
## From Request to Delivery
|
|
65
65
|
|
|
66
66
|
1. **Describe the goal.** Explain who will use the app, what they need to do, and any existing app or reference material.
|
|
67
|
-
2. **
|
|
67
|
+
2. **Share requirements and preferences.** Add the business details and appearance preferences that matter to you. Include any colors, websites, or design images you like.
|
|
68
68
|
3. **Choose how to build.** Fast suits clear requirements you want to move forward with. Plan produces a reviewable proposal and waits for approval before creating resources.
|
|
69
69
|
4. **Review and refine.** Check the delivered app and common actions, then request page, field, or workflow changes. Your chosen style is reused unless you ask to change it.
|
|
70
70
|
|
|
@@ -72,7 +72,7 @@ For existing-app improvements, review the visual proposal before backups and imp
|
|
|
72
72
|
|
|
73
73
|
## Recent Updates
|
|
74
74
|
|
|
75
|
-
**2026.10.
|
|
75
|
+
**2026.10.14-1** improves appearance customization, with better support for style descriptions, reference websites, and design images. [Read the full changelog](./CHANGELOG.md#20261014-1---2026-10-14).
|
|
76
76
|
|
|
77
77
|
## Quick Start
|
|
78
78
|
|
|
@@ -443,6 +443,9 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
443
443
|
| `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--rebase-parts] [--check] [--json]` | Generate or update plan documents and theme with consistency checks |
|
|
444
444
|
| `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch plan fields and optionally sync documents and theme |
|
|
445
445
|
| `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--confirm-legacy-app-style] [--disable-custom-theme] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
|
|
446
|
+
| `openyida data-manage-style guide [--json]` | Inspect data-management UI directions, tokens and scoped CSS guidance |
|
|
447
|
+
| `openyida data-manage-style list [--json]` | List optional data-management recipe examples |
|
|
448
|
+
| `openyida data-manage-style generate --app <appType> --form <formUuid> --style <id> --base-css <theme.css> --output <new.css> [--focus balanced\|actions] [--field-id <actualId>]... [--emphasize-field <actualId>]... [--tag-field <selectFieldId>]... [--follow-shell] [--json]` | Generate data-management CSS from an optional recipe (local only) |
|
|
446
449
|
| `openyida app-theme snapshot <appType> --output-dir <new-dir> [--json]` | Archive application theme settings and CSS (read-only) |
|
|
447
450
|
| `openyida app-theme restore <appType> --snapshot <before.json> --expected-current <after.json> [--check \| --confirm --output-dir <new-dir>] [--json]` | Check or restore an application theme snapshot (supported exact values only) |
|
|
448
451
|
| `openyida upgrade-app-theme <appType> --explicit-request [--confirm \| --prepare --output-dir <dir>] [--json]` | Upgrade an AI-built legacy app theme (explicit request and ask_human confirmation required) |
|
|
@@ -633,30 +636,17 @@ Form field definitions can include `alias` or `componentAlias` to populate Yida
|
|
|
633
636
|
|
|
634
637
|
`openyida integration create` supports form events (`insert`, `update`, `delete`, `comment`) and approval events (`processFinish`, `activityTask`; aliases: `approval`, `approvalNode`). Approval events require `--approval-actions agree,disagree,terminated`; `activityTask` also requires `--approval-node-ids <nodeId,...>`. Passing `--process-code` selects a full graph replacement and now requires explicit `--replace`; it is not a safe update. `integration update` currently performs capability detection only: because full platform `processJson` + `viewJson` readback is unproven, it writes a redacted local probe artifact and returns `PLATFORM_PROBE_REQUIRED` before authentication, spec reading, or remote writes. It does not edit a flow.
|
|
635
638
|
|
|
636
|
-
### Local Agent
|
|
639
|
+
### Connect a Local Agent
|
|
637
640
|
|
|
638
|
-
|
|
641
|
+
Use an installed Qoder or Codex CLI on your computer to run tasks from the Yida web app. Local Agent supports macOS and Windows; the ordinary OpenYida CLI also supports Linux.
|
|
639
642
|
|
|
640
|
-
|
|
643
|
+
Check your local environment, then follow the connection instructions in the Yida web app:
|
|
641
644
|
|
|
642
645
|
```bash
|
|
643
646
|
openyida agent doctor --json
|
|
644
|
-
openyida agent doctor --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json
|
|
645
|
-
openyida agent connect --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id
|
|
646
|
-
openyida agent run --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id --provider qoder --provider-path /absolute/path/qodercli
|
|
647
647
|
```
|
|
648
648
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
`connect` supports two equivalent enrollment entries. CLI-first opens the Yida browser approval page and returns through a state/PKCE-protected loopback callback. Web-first copies one five-minute command containing `--enroll`; the token is single-use and carries no business authority. Neither path reads or uploads an ordinary OpenYida login profile.
|
|
652
|
-
|
|
653
|
-
One installation may connect to multiple organizations. Node keeps only a shared Installation ID at the installation root; each organization authorization gets an isolated private Connection state directory, device credentials, journals, control socket, and frozen bundle. `agent connect` and `agent run` start an independent background daemon by default and return once it is ready. `status`, `logs`, `disconnect`, and `logout` enumerate valid Connections. Background mode is not boot-time autostart. Each Connection registers all locally discovered Qoder and Codex Runtimes, and the web UI selects computer, CLI, and model.
|
|
654
|
-
|
|
655
|
-
For each attempt, Go injects `OPENYIDA_MANAGED_RUN=1`, the cloud user/corp/app/run/attempt context, and a fixed-value Tianshu task grant with an initial 60-minute lease. The CLI uses that grant instead of local auth and sends scope-correlation headers. While the authenticated device connection and server-side Run remain active, yida-agent can renew the same bearer server-to-server; the CLI cannot renew it and never receives a task refresh token. Tianshu independently checks the grant against the requested app and rejects application creation, identity/environment changes, cross-application requests and unsupported organization-wide operations; declared application-bound batches are supported. `formUuid` remains optional per-turn page-focus context, so one app-bound session can work across pages in that application. Without `OPENYIDA_MANAGED_RUN`, existing login, token refresh, and command behavior are unchanged.
|
|
656
|
-
|
|
657
|
-
Before `connect` or `run`, Node now freezes the verified runtime bytes, CLI, full production dependency closure, and packaged Skills into a private content-addressed bundle. Source changes during copying fail closed. Existing bundles are verified, never silently repaired or garbage-collected by Node, and survive replacement/removal of the original npm directory. Go owns active/session references and version activation; the external Node executable and provider installations are not copied or managed by npm. A separate parent-watch pipe and a 15-second stop grace prevent normal config EOF from being mistaken for parent death; an unconfirmed forced stop remains unknown.
|
|
658
|
-
|
|
659
|
-
This is not an OS sandbox or an authorization boundary against arbitrary shell code. Production signing/release infrastructure, full Go/provider recovery and upgrade acceptance, and OS/architecture-specific provider E2E are not claimed by these fake-process tests. Auto-update is disabled for `agent` and managed invocations; ordinary CLI commands neither spawn nor download the runtime.
|
|
649
|
+
After connecting, the service runs in the background. Use `openyida agent status` to check its status, `openyida agent logs` to view logs, and `openyida agent disconnect` to disconnect. After restarting your computer, run `openyida agent run` to reconnect.
|
|
660
650
|
|
|
661
651
|
## Agent Skills
|
|
662
652
|
|
package/bin/yida.js
CHANGED
|
@@ -942,6 +942,11 @@ async function main() {
|
|
|
942
942
|
break;
|
|
943
943
|
}
|
|
944
944
|
|
|
945
|
+
case 'data-manage-style': {
|
|
946
|
+
await require('../lib/style/data-manage-style').run(args);
|
|
947
|
+
break;
|
|
948
|
+
}
|
|
949
|
+
|
|
945
950
|
case 'app-theme': {
|
|
946
951
|
await require('../lib/app/app-theme').run(args);
|
|
947
952
|
break;
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { CliError } = require('../core/cli-error');
|
|
6
|
+
const { randomUUID } = require('crypto');
|
|
7
|
+
const { mkdirPrivate, mkdirPrivateDirectories } = require('./private-directory');
|
|
6
8
|
const { t } = require('../core/i18n');
|
|
7
|
-
const { digest, readRegularFile, platformPackageName } = require('./runtime-package');
|
|
8
|
-
const { parseStrictJson
|
|
9
|
+
const { digest, readRegularFile, platformPackageName, canonicalPath } = require('./runtime-package');
|
|
10
|
+
const { parseStrictJson } = require('./runtime-manifest');
|
|
9
11
|
|
|
10
12
|
const MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
11
13
|
const MAX_BUNDLE_BYTES = 1024 * 1024 * 1024;
|
|
@@ -37,7 +39,7 @@ function privateDirectory(directory) {
|
|
|
37
39
|
let ancestor = directory;
|
|
38
40
|
while (!fs.existsSync(ancestor)) {suffix.unshift(path.basename(ancestor)); ancestor = path.dirname(ancestor);}
|
|
39
41
|
if (fs.lstatSync(ancestor).isSymbolicLink()) {throw bundleError();}
|
|
40
|
-
let current =
|
|
42
|
+
let current = canonicalPath(ancestor);
|
|
41
43
|
const uid = typeof process.getuid === 'function' ? process.getuid() : null;
|
|
42
44
|
for (let parent = current; ; parent = path.dirname(parent)) {
|
|
43
45
|
const stat = fs.lstatSync(parent);
|
|
@@ -46,6 +48,7 @@ function privateDirectory(directory) {
|
|
|
46
48
|
(stat.mode & 0o022) && !(stat.uid === 0 && stat.mode & 0o1000))) {throw bundleError('AGENT_BUNDLE_UNSAFE_DIRECTORY');}
|
|
47
49
|
if (parent === path.dirname(parent)) {break;}
|
|
48
50
|
}
|
|
51
|
+
if (process.platform === 'win32' && suffix.length) {mkdirPrivate(path.join(current, ...suffix));}
|
|
49
52
|
for (const component of suffix) {
|
|
50
53
|
current = path.join(current, component);
|
|
51
54
|
try {fs.mkdirSync(current, { mode: 0o700 });} catch (error) {if (error.code !== 'EEXIST') {throw error;}}
|
|
@@ -67,7 +70,7 @@ function findDependency(root, name) {
|
|
|
67
70
|
// npm subprocess, or an install hook. Preserve each package's own resolution.
|
|
68
71
|
for (let current = root; ; current = path.dirname(current)) {
|
|
69
72
|
const candidate = path.join(current, 'node_modules', name);
|
|
70
|
-
if (fs.existsSync(path.join(candidate, 'package.json'))) {return
|
|
73
|
+
if (fs.existsSync(path.join(candidate, 'package.json'))) {return canonicalPath(candidate);}
|
|
71
74
|
if (current === path.dirname(current)) {return null;}
|
|
72
75
|
}
|
|
73
76
|
}
|
|
@@ -126,7 +129,7 @@ function verifyBundle(root, expectedId) {
|
|
|
126
129
|
}
|
|
127
130
|
|
|
128
131
|
function materializeBundle(runtime, config, options = {}) {
|
|
129
|
-
const sourceRoot =
|
|
132
|
+
const sourceRoot = canonicalPath(config.node.packageRoot);
|
|
130
133
|
const cache = privateDirectory(options.bundleCache || path.join(config.stateDir, 'bundles'));
|
|
131
134
|
let stage;
|
|
132
135
|
const pendingFiles = [];
|
|
@@ -197,10 +200,7 @@ function materializeBundle(runtime, config, options = {}) {
|
|
|
197
200
|
try {
|
|
198
201
|
const metadata = packageMetadata(sourceRoot);
|
|
199
202
|
if (metadata.name !== 'openyida' || typeof metadata.version !== 'string') {throw bundleError();}
|
|
200
|
-
|
|
201
|
-
// runtime package. Keep the production gate and supported-package exclusion.
|
|
202
|
-
runtimePackage = runtime.development === true && !PRODUCTION_RUNTIME_PLATFORMS.includes(process.platform)
|
|
203
|
-
? undefined : platformPackageName(process.platform, process.arch);
|
|
203
|
+
runtimePackage = runtime.development === true ? `@openyida/local-agent-runtime-${process.platform}-${process.arch}` : platformPackageName(process.platform, process.arch);
|
|
204
204
|
// Deliberate installed CLI allowlist: no checkout, private state, user
|
|
205
205
|
// project, devDependencies, postinstall execution, or global Skills links.
|
|
206
206
|
for (const directory of ['bin', 'lib', 'yida-skills']) {
|
|
@@ -256,13 +256,23 @@ function materializeBundle(runtime, config, options = {}) {
|
|
|
256
256
|
const id = digest(raw);
|
|
257
257
|
const root = path.join(cache, id);
|
|
258
258
|
if (!fs.existsSync(root)) {
|
|
259
|
-
|
|
259
|
+
// Windows may otherwise assign the elevated process's Administrators
|
|
260
|
+
// group as owner. Go requires the current user, as for credential state.
|
|
261
|
+
if (process.platform === 'win32') {
|
|
262
|
+
stage = path.join(cache, '.staging-' + randomUUID());
|
|
263
|
+
mkdirPrivate(stage);
|
|
264
|
+
} else {stage = fs.mkdtempSync(path.join(cache, '.staging-'));}
|
|
260
265
|
fs.chmodSync(stage, 0o700);
|
|
266
|
+
if (process.platform === 'win32') {
|
|
267
|
+
// Inherited DACLs do not bind nested owners under an elevated token.
|
|
268
|
+
// Create every parent privately in one PowerShell invocation before writes.
|
|
269
|
+
mkdirPrivateDirectories(pendingFiles.map(file => path.dirname(path.join(stage, file.relative))));
|
|
270
|
+
}
|
|
261
271
|
for (const file of pendingFiles) {
|
|
262
272
|
const bytes = file.source ? readRegularFile(file.source, MAX_FILE_BYTES).bytes : file.bytes;
|
|
263
273
|
if (digest(bytes) !== file.sha256) {throw bundleError('AGENT_BUNDLE_SOURCE_CHANGED');}
|
|
264
274
|
const target = path.join(stage, file.relative);
|
|
265
|
-
fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });
|
|
275
|
+
if (process.platform !== 'win32') {fs.mkdirSync(path.dirname(target), { recursive: true, mode: 0o700 });}
|
|
266
276
|
fs.writeFileSync(target, bytes, { flag: 'wx', mode: file.mode });
|
|
267
277
|
fs.chmodSync(target, file.mode);
|
|
268
278
|
}
|
|
@@ -13,26 +13,32 @@ function directoryError() {
|
|
|
13
13
|
// Node's mkdir mode is ignored on Windows. Create each missing directory with
|
|
14
14
|
// an inheritable private DACL before any credentials or logs are written.
|
|
15
15
|
// Existing directories are never re-permissioned here.
|
|
16
|
-
function
|
|
16
|
+
function mkdirPrivateDirectories(directories) {
|
|
17
17
|
if (process.platform !== 'win32') {
|
|
18
|
-
fs.mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
18
|
+
for (const directory of directories) {fs.mkdirSync(directory, { recursive: true, mode: 0o700 });}
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
21
|
+
const targets = [...new Set(directories.map(directory => path.resolve(directory)))];
|
|
22
|
+
const pending = new Set();
|
|
23
|
+
for (const target of targets) {
|
|
24
|
+
const missing = [];
|
|
25
|
+
let ancestor = target;
|
|
26
|
+
while (!fs.existsSync(ancestor)) {
|
|
27
|
+
missing.unshift(ancestor);
|
|
28
|
+
ancestor = path.dirname(ancestor);
|
|
29
|
+
}
|
|
30
|
+
for (let parent = ancestor; ; parent = path.dirname(parent)) {
|
|
31
|
+
const stat = fs.lstatSync(parent);
|
|
32
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {throw directoryError();}
|
|
33
|
+
if (parent === path.dirname(parent)) {break;}
|
|
34
|
+
}
|
|
35
|
+
for (const directory of missing) {pending.add(directory);}
|
|
32
36
|
}
|
|
37
|
+
const missing = [...pending];
|
|
33
38
|
if (!missing.length) {return;}
|
|
34
39
|
const script = `
|
|
35
40
|
$ErrorActionPreference = 'Stop'
|
|
41
|
+
$reader = [System.IO.StreamReader]::new([Console]::OpenStandardInput(), [System.Text.Encoding]::UTF8)
|
|
36
42
|
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
37
43
|
$acl = New-Object System.Security.AccessControl.DirectorySecurity
|
|
38
44
|
$acl.SetOwner($identity)
|
|
@@ -42,22 +48,36 @@ foreach ($sid in @($identity.Value, 'S-1-5-18', 'S-1-5-32-544')) {
|
|
|
42
48
|
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($principal, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow')
|
|
43
49
|
$acl.AddAccessRule($rule)
|
|
44
50
|
}
|
|
45
|
-
foreach ($directory in ($
|
|
46
|
-
|
|
51
|
+
foreach ($directory in ($reader.ReadToEnd() | ConvertFrom-Json)) {
|
|
52
|
+
# Concurrent launchers may create the same parent. Never rewrite a winner;
|
|
53
|
+
# accept it only when its actual owner and grants meet the same private policy.
|
|
47
54
|
[System.IO.Directory]::CreateDirectory($directory, $acl) | Out-Null
|
|
55
|
+
$actual = (New-Object System.IO.DirectoryInfo($directory)).GetAccessControl()
|
|
56
|
+
if ($actual.GetOwner([System.Security.Principal.SecurityIdentifier]).Value -ne $identity.Value) {throw 'Private directory owner differs'}
|
|
57
|
+
foreach ($rule in $actual.Access) {
|
|
58
|
+
$sid = $rule.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
59
|
+
if ($rule.AccessControlType -ne 'Allow' -or $sid -notin @($identity.Value, 'S-1-5-18', 'S-1-5-32-544')) {throw 'Private directory access differs'}
|
|
60
|
+
}
|
|
48
61
|
}
|
|
49
62
|
`;
|
|
50
63
|
try {
|
|
51
64
|
execFileSync(path.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
|
|
52
65
|
['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
53
66
|
windowsHide: true, timeout: 15000, stdio: 'pipe',
|
|
54
|
-
|
|
67
|
+
// Stdin avoids Windows environment-variable size limits for full bundles.
|
|
68
|
+
input: JSON.stringify(missing),
|
|
55
69
|
});
|
|
56
70
|
} catch {
|
|
57
71
|
throw directoryError();
|
|
58
72
|
}
|
|
59
|
-
const
|
|
60
|
-
|
|
73
|
+
for (const target of targets) {
|
|
74
|
+
const stat = fs.lstatSync(target);
|
|
75
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {throw directoryError();}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function mkdirPrivate(directory) {
|
|
80
|
+
mkdirPrivateDirectories([directory]);
|
|
61
81
|
}
|
|
62
82
|
|
|
63
|
-
module.exports = { mkdirPrivate };
|
|
83
|
+
module.exports = { mkdirPrivate, mkdirPrivateDirectories };
|
|
@@ -99,18 +99,14 @@ function discoverProviders(env = process.env, platform = process.platform, home)
|
|
|
99
99
|
}));
|
|
100
100
|
}
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
return `'${String(value).replace(/'/g, "''")}'`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Match the Go Runtime's npm launcher handling without cmd.exe interpolation.
|
|
102
|
+
// Use the npm PowerShell sibling without cmd.exe or command-string interpolation.
|
|
107
103
|
function probeCommand(executable, args, platform = process.platform, env = process.env) {
|
|
108
104
|
if (platform !== 'win32' || !/\.(cmd|bat)$/i.test(executable)) { return { executable, args }; }
|
|
109
105
|
const script = executable.replace(/\.(cmd|bat)$/i, '.ps1');
|
|
110
106
|
if (!exists(script)) { throw new Error('npm launcher has no PowerShell sibling; select a native CLI'); }
|
|
111
|
-
const command = `& ${[script, ...args].map(powerShellLiteral).join(' ')}`;
|
|
112
107
|
return { executable: path.join(env.SystemRoot || env.SYSTEMROOT || 'C:\\Windows', 'System32/WindowsPowerShell/v1.0/powershell.exe'),
|
|
113
|
-
|
|
108
|
+
// -File keeps redirected stdin available to ACP and passes arguments as data.
|
|
109
|
+
args: ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', script, ...args] };
|
|
114
110
|
}
|
|
115
111
|
|
|
116
112
|
// No prompts, model calls, login flows or provider output enter our report.
|
|
@@ -10,33 +10,33 @@
|
|
|
10
10
|
"platform": "darwin",
|
|
11
11
|
"arch": "arm64",
|
|
12
12
|
"packageName": "@openyida/local-agent-runtime-darwin-arm64",
|
|
13
|
-
"packageVersion": "0.1.
|
|
14
|
-
"runtimeVersion": "0.1.
|
|
15
|
-
"manifestSha256": "
|
|
13
|
+
"packageVersion": "0.1.2",
|
|
14
|
+
"runtimeVersion": "0.1.2",
|
|
15
|
+
"manifestSha256": "98909c5b049544aa287abb781796a6b2261948eae8a4eb4ee6a345edc3ebb8b4"
|
|
16
16
|
},
|
|
17
17
|
{
|
|
18
18
|
"platform": "darwin",
|
|
19
19
|
"arch": "x64",
|
|
20
20
|
"packageName": "@openyida/local-agent-runtime-darwin-x64",
|
|
21
|
-
"packageVersion": "0.1.
|
|
22
|
-
"runtimeVersion": "0.1.
|
|
23
|
-
"manifestSha256": "
|
|
21
|
+
"packageVersion": "0.1.2",
|
|
22
|
+
"runtimeVersion": "0.1.2",
|
|
23
|
+
"manifestSha256": "29d95d796890fcf6047e79e1a101d68f70506bcd53a5e813d19bf7b833ddb899"
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
"platform": "win32",
|
|
27
27
|
"arch": "x64",
|
|
28
28
|
"packageName": "@openyida/local-agent-runtime-win32-x64",
|
|
29
|
-
"packageVersion": "0.1.
|
|
30
|
-
"runtimeVersion": "0.1.
|
|
31
|
-
"manifestSha256": "
|
|
29
|
+
"packageVersion": "0.1.2",
|
|
30
|
+
"runtimeVersion": "0.1.2",
|
|
31
|
+
"manifestSha256": "44aff218711c120d4d24e3c34ce2867a34d6f78bab5c1682a4808b4b9c3cfd56"
|
|
32
32
|
},
|
|
33
33
|
{
|
|
34
34
|
"platform": "win32",
|
|
35
35
|
"arch": "arm64",
|
|
36
36
|
"packageName": "@openyida/local-agent-runtime-win32-arm64",
|
|
37
|
-
"packageVersion": "0.1.
|
|
38
|
-
"runtimeVersion": "0.1.
|
|
39
|
-
"manifestSha256": "
|
|
37
|
+
"packageVersion": "0.1.2",
|
|
38
|
+
"runtimeVersion": "0.1.2",
|
|
39
|
+
"manifestSha256": "742e21e97f7c72ca78030d43f10d42e7470da2aabc0af6a8536dde6767390680"
|
|
40
40
|
}
|
|
41
41
|
]
|
|
42
42
|
}
|
|
@@ -11,6 +11,10 @@ const bundledCatalog = require('./runtime-catalog.json');
|
|
|
11
11
|
|
|
12
12
|
const PROTOCOL_VERSION = 1;
|
|
13
13
|
|
|
14
|
+
function canonicalPath(value) {
|
|
15
|
+
return process.platform === 'win32' ? fs.realpathSync.native(value) : fs.realpathSync(value);
|
|
16
|
+
}
|
|
17
|
+
|
|
14
18
|
function runtimeError(code, key) {
|
|
15
19
|
return new CliError(t(`agent.${key}`), { code });
|
|
16
20
|
}
|
|
@@ -28,7 +32,7 @@ function requireAbsoluteFile(value, code) {
|
|
|
28
32
|
if (!stat.isFile() || stat.isSymbolicLink()) {
|
|
29
33
|
throw runtimeError(code, 'unsafe_file');
|
|
30
34
|
}
|
|
31
|
-
return { path:
|
|
35
|
+
return { path: canonicalPath(value), stat };
|
|
32
36
|
}
|
|
33
37
|
|
|
34
38
|
function readRegularFile(filename, maxBytes, code = 'AGENT_RUNTIME_INVALID_PATH') {
|
|
@@ -122,7 +126,7 @@ function resolveProduction(options) {
|
|
|
122
126
|
const main = parseStrictJson(readRegularFile(path.join(packageRoot, 'package.json'), MAX_MANIFEST_BYTES).bytes);
|
|
123
127
|
if (!main.optionalDependencies || main.optionalDependencies[name] !== release.packageVersion) {throw new Error('unapproved dependency');}
|
|
124
128
|
const localRequire = createRequire(path.join(packageRoot, 'package.json'));
|
|
125
|
-
installedRoot = path.dirname(
|
|
129
|
+
installedRoot = path.dirname(canonicalPath(localRequire.resolve(`${name}/package.json`)));
|
|
126
130
|
const metadata = parseStrictJson(readRegularFile(safePackageFile(installedRoot, 'package.json'), MAX_MANIFEST_BYTES).bytes);
|
|
127
131
|
if (metadata.name !== name || metadata.version !== release.packageVersion ||
|
|
128
132
|
!Array.isArray(metadata.os) || metadata.os.length !== 1 || metadata.os[0] !== platform ||
|
|
@@ -152,4 +156,4 @@ function resolveRuntime(options = {}) {
|
|
|
152
156
|
return resolveProduction(options);
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
module.exports = { PROTOCOL_VERSION, resolveRuntime, requireAbsoluteFile, readRegularFile, digest, platformPackageName };
|
|
159
|
+
module.exports = { PROTOCOL_VERSION, resolveRuntime, requireAbsoluteFile, readRegularFile, digest, platformPackageName, canonicalPath };
|
package/lib/agent/stdio.js
CHANGED
|
@@ -69,6 +69,7 @@ function launchRuntime(runtime, config, options = {}) {
|
|
|
69
69
|
const watchesParent = ['connect', 'run'].includes(config.command);
|
|
70
70
|
if (watchesParent) {config = process.platform === 'win32'
|
|
71
71
|
? { ...config, parentPID: process.pid } : { ...config, parentWatchFD: 3 };}
|
|
72
|
+
const watchesPipe = watchesParent && process.platform !== 'win32';
|
|
72
73
|
const payload = JSON.stringify(config);
|
|
73
74
|
if (Buffer.byteLength(payload) > MAX_CONFIG_BYTES) {
|
|
74
75
|
throw new CliError(t('agent.input_invalid'), { code: 'AGENT_INPUT_INVALID' });
|
|
@@ -137,7 +138,11 @@ function launchRuntime(runtime, config, options = {}) {
|
|
|
137
138
|
child = (options.spawn || spawn)(runtime.executable, [config.command, '--stdio-config'], {
|
|
138
139
|
cwd: config.node.packageRoot,
|
|
139
140
|
env: runtimeEnvironment(options.env),
|
|
140
|
-
stdio:
|
|
141
|
+
stdio: watchesPipe ? ['pipe', 'pipe', 'pipe', 'pipe'] : ['pipe', 'pipe', 'pipe'],
|
|
142
|
+
// Escape libuv's kill-on-parent-close job on Windows. The native PID
|
|
143
|
+
// handle monitor owns cancellation/draining; retain our Node handle
|
|
144
|
+
// (no unref) so normal foreground execution still waits for completion.
|
|
145
|
+
detached: process.platform === 'win32' && watchesParent,
|
|
141
146
|
windowsHide: true,
|
|
142
147
|
shell: false,
|
|
143
148
|
});
|
|
@@ -149,10 +154,10 @@ function launchRuntime(runtime, config, options = {}) {
|
|
|
149
154
|
}
|
|
150
155
|
signals.on('SIGINT', forwardInterrupt);
|
|
151
156
|
signals.on('SIGTERM', forwardTerminate);
|
|
152
|
-
// Config stdin intentionally reaches EOF after one JSON.
|
|
153
|
-
// pipe remains open for the parent lifetime; Go treats EOF as stop/drain,
|
|
157
|
+
// Config stdin intentionally reaches EOF after one JSON. On Unix this
|
|
158
|
+
// separate pipe remains open for the parent lifetime; Go treats EOF as stop/drain,
|
|
154
159
|
// not successful completion. Do not send credentials on the watch pipe.
|
|
155
|
-
const parentWatch =
|
|
160
|
+
const parentWatch = watchesPipe && child.stdio && child.stdio[3];
|
|
156
161
|
if (parentWatch) {parentWatch.on('error', () => {if (!closed) {fail('AGENT_RUNTIME_PARENT_WATCH_FAILED');}});}
|
|
157
162
|
child.stdout.on('data', (chunk) => {
|
|
158
163
|
pending += decoder.write(chunk);
|
package/lib/app/app-entry.js
CHANGED
|
@@ -4,6 +4,8 @@ const { createAuthRef, createYidaClient, unwrapYidaResponse } = require('../core
|
|
|
4
4
|
const { t } = require('../core/i18n');
|
|
5
5
|
const { throwUsage } = require('../core/command-errors');
|
|
6
6
|
const { fetchFormPageList } = require('./form-navigation');
|
|
7
|
+
const { fetchFormSchemaInfo } = require('./get-form-config');
|
|
8
|
+
const { buildDisplayPageEntryUrls } = require('./application-entry-urls');
|
|
7
9
|
|
|
8
10
|
/** Keep complete runtime URLs; the server verifies tenant and resource ownership. */
|
|
9
11
|
function normalizeUrl(value, appType) {
|
|
@@ -54,7 +56,16 @@ async function resolveEntryUrl(authRef, appType, key, formUuid) {
|
|
|
54
56
|
if (key === 'frontend') {
|
|
55
57
|
if (String(form.formType).toLowerCase() !== 'display') { throw new Error(t('app_entry.frontend_display_required', formUuid)); }
|
|
56
58
|
if (!form.pathName) { throw new Error(t('app_entry.page_offline', formUuid)); }
|
|
57
|
-
|
|
59
|
+
const response = await fetchFormSchemaInfo(authRef, appType, formUuid);
|
|
60
|
+
if (!response || response.success === false || response.__needLogin || response.__csrfExpired) {
|
|
61
|
+
throw new Error(t('app_entry.invalid_response'));
|
|
62
|
+
}
|
|
63
|
+
const config = response.content || response.result || response.data;
|
|
64
|
+
const entry = buildDisplayPageEntryUrls({ appType, formUuid, baseUrl: authRef.baseUrl, config, formType: 'display' });
|
|
65
|
+
if (!entry.standaloneUrl || entry.navigationVerification?.renderNav !== false) {
|
|
66
|
+
throw new Error(t('app_entry.frontend_nav_required', formUuid));
|
|
67
|
+
}
|
|
68
|
+
return normalizeUrl(entry.standaloneUrl, appType);
|
|
58
69
|
}
|
|
59
70
|
return normalizeUrl(`${authRef.baseUrl}/${appType}/workbench/${formUuid}`, appType);
|
|
60
71
|
}
|
|
@@ -137,8 +148,10 @@ async function run(args = []) {
|
|
|
137
148
|
urls[key] = normalizeUrl(result.accessEntries[key].url, appType);
|
|
138
149
|
}
|
|
139
150
|
}
|
|
140
|
-
|
|
141
|
-
|
|
151
|
+
const output = { ...result, appType, urls,
|
|
152
|
+
verification: { configurationReadback: true, runtimeVerified: false } };
|
|
153
|
+
console.log(JSON.stringify(output, null, 2));
|
|
154
|
+
return output;
|
|
142
155
|
}
|
|
143
156
|
|
|
144
157
|
module.exports = { run, parseArgs, normalizeUrl, resolveEntryUrl };
|
|
@@ -5,6 +5,7 @@ const { t } = require('../core/i18n');
|
|
|
5
5
|
const { buildYidaTitleI18n, normalizeYidaLocale, resolveContentLocale } = require('../core/yida-i18n');
|
|
6
6
|
const { banner, step, label, success, fail, warn, info, error, result, usage } = require('../core/chalk');
|
|
7
7
|
const { throwCommandError, throwUsage } = require('../core/command-errors');
|
|
8
|
+
const { readRenderNav } = require('./application-entry-urls');
|
|
8
9
|
|
|
9
10
|
function parseArgs(inputArgs = process.argv.slice(2)) {
|
|
10
11
|
const args = [...inputArgs];
|
|
@@ -158,12 +159,14 @@ async function run(args = process.argv.slice(2)) {
|
|
|
158
159
|
success(t('update_form_config.read_ok'));
|
|
159
160
|
|
|
160
161
|
// A keep request must not silently switch navigation when the read is incomplete.
|
|
161
|
-
|
|
162
|
+
const currentRenderNav = readRenderNav(currentConfig);
|
|
163
|
+
if (keepNav && currentRenderNav === null) {
|
|
162
164
|
throwCommandError(t('get_form_config.read_failed', t('common.request_failed_label')), {
|
|
163
165
|
code: 'CONFIG_READ_FAILED',
|
|
164
166
|
});
|
|
165
167
|
}
|
|
166
|
-
|
|
168
|
+
// The read API exposes renderNav; the write API still accepts isRenderNav.
|
|
169
|
+
const effectiveNav = keepNav ? String(currentRenderNav) : isRenderNav;
|
|
167
170
|
|
|
168
171
|
step(3, t('update_form_config.step_update'));
|
|
169
172
|
info(t('update_form_config.sending_request'));
|
|
@@ -472,6 +472,7 @@ function buildFullAppArtifactRoute(manifest) {
|
|
|
472
472
|
user_visible_expression_policy: workflow.user_visible_expression_policy,
|
|
473
473
|
optional_asset_branch: workflow.optional_asset_branch,
|
|
474
474
|
design_mode_policy: workflow.design_mode_policy,
|
|
475
|
+
plan_confirmation_policy: workflow.plan_confirmation_policy,
|
|
475
476
|
navigation_policy: workflow.navigation_policy,
|
|
476
477
|
form_entry_policy: workflow.form_entry_policy,
|
|
477
478
|
divider_style_contract: workflow.divider_style_contract,
|